-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Google Social Authentication throws Invalid id_token
#503
Comments
allauth.socialaccount.providers.oauth2.client.OAuth2Error: Invalid id_token
Invalid id_token
I had this same problem and came to the similar conclusion. In my case I overrode GoogleOAuth2Adapter and changed |
Yeah, I only passed access_token to dj_rest_auth endpoint and it worked, as
there was nothing as id_token returned from the Google client library..
…On Mon, 24 Apr 2023, 16:54 Nik Tomazic, ***@***.***> wrote:
I'm using dj-rest-auth on the backend and NextAuth.js
<https://next-auth.js.org/> on the frontend. After successfully logging
in via NextAuth.js I get the following account passed to my signIn
callback:
{
provider: "google",
type: "oauth",
providerAccountId: "177780422328299215542",
access_token: "%access_token%",
expires_at: 1682337884,
refresh_token: "%refresh_token%",
scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid",
token_type: "Bearer",
id_token: "%id_token%"
}
1. access_token is a random string of characters. Not decodable by
jwt.io.
2. id_token is a JWT token (header.payload.signature). It is decodable
by jwt.io.
I then forward the access_token and id_token to my dj-rest-auth Google
endpoint defined like this:
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapterfrom allauth.socialaccount.providers.oauth2.client import OAuth2Client, OAuth2Errorfrom dj_rest_auth.registration.views import SocialLoginView
class GoogleLogin(SocialLoginView): # Authorization Code grant
adapter_class = GoogleOAuth2Adapter
callback_url = "http://localhost:3000/api/auth/callback/google"
client_class = OAuth2Client
I use the following request code:
const options = {
method: 'POST',
url: 'http://127.0.0.1:8000/api/auth/google/',
headers: {
'Content-Type': 'application/json'
},
data: {
access_token: '%access_token%',
id_token: '%id_token%'
}};
axios.request(options).then(function (response) {
console.log(response.data);}).catch(function (error) {
console.error(error);});
This request fails with an error saying:
Not enough segments
Internal Server Error: /api/auth/google/
Traceback (most recent call last):
File ".\venv\lib\site-packages\jwt\api_jws.py", line 251, in _load
header_segment, payload_segment = signing_input.split(b".", 1)
ValueError: not enough values to unpack (expected 2, got 1)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".\authentication\views.py", line 33, in complete_login
identity_data = jwt.decode(
File ".\venv\lib\site-packages\jwt\api_jwt.py", line 168, in decode
decoded = self.decode_complete(
File ".\venv\lib\site-packages\jwt\api_jwt.py", line 120, in decode_complete
decoded = api_jws.decode_complete(
File ".\venv\lib\site-packages\jwt\api_jws.py", line 191, in decode_complete
payload, signing_input, header, signature = self._load(jwt)
File ".\venv\lib\site-packages\jwt\api_jws.py", line 253, in _load
raise DecodeError("Not enough segments") from err
jwt.exceptions.DecodeError: Not enough segments
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
File ".\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File ".\venv\lib\site-packages\django\views\decorators\csrf.py", line 56, in wrapper_view
return view_func(*args, **kwargs)
File ".\venv\lib\site-packages\django\views\generic\base.py", line 104, in view
return self.dispatch(request, *args, **kwargs)
File ".\venv\lib\site-packages\django\utils\decorators.py", line 46, in _wrapper
return bound_method(*args, **kwargs)
File ".\venv\lib\site-packages\django\views\decorators\debug.py", line 92, in sensitive_post_parameters_wrapper
return view(request, *args, **kwargs)
File ".\venv\lib\site-packages\dj_rest_auth\views.py", line 48, in dispatch
return super().dispatch(*args, **kwargs)
File ".\venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
response = self.handle_exception(exc)
File ".\venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File ".\venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
raise exc
File ".\venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File ".\venv\lib\site-packages\dj_rest_auth\views.py", line 125, in post
self.serializer.is_valid(raise_exception=True)
File ".\venv\lib\site-packages\rest_framework\serializers.py", line 227, in is_valid
self._validated_data = self.run_validation(self.initial_data)
File ".\venv\lib\site-packages\rest_framework\serializers.py", line 429, in run_validation
value = self.validate(value)
File ".\venv\lib\site-packages\dj_rest_auth\registration\serializers.py", line 151, in validate
login = self.get_social_login(adapter, app, social_token, response={'id_token': token})
File ".\venv\lib\site-packages\dj_rest_auth\registration\serializers.py", line 60, in get_social_login
social_login = adapter.complete_login(request, app, token, response=response)
File ".\authentication\views.py", line 46, in complete_login
raise OAuth2Error("Invalid id_token") from e
allauth.socialaccount.providers.oauth2.client.OAuth2Error: Invalid id_token
------------------------------
After some testing I figured out that response["id_token"] in
complete_login returns the request's access_token and not the id_token.
print(response["id_token"])# prints out {'id_token': '%access_token%'}
Sending your id_token as the access_token without providing id_token
seems to work:
const options = {
method: 'POST',
url: 'http://127.0.0.1:8000/api/auth/google/',
headers: {
'Content-Type': 'application/json'
},
data: {
access_token: '%id_token%'
}};
axios.request(options).then(function (response) {
console.log(response.data);}).catch(function (error) {
console.error(error);});
Is this the expected behaviour? Am I missing something?
—
Reply to this email directly, view it on GitHub
<#503>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ASMTYPFSD6SPWZ4HZRZX2Y3XCZSXPANCNFSM6AAAAAAXJO35P4>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
I'm very resistant to passing id_token to access_token, but it works anyway. |
@agent-Y do you have |
@cplanck Actually I'm using dj-rest-auth with djangorestframework-simplejwt (https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html# ), so I guess it is not dj-rest-auth's fault. |
This error only occurs with dj-rest-auth version 3.0.0. I fixed downgraded to2.2.8. |
I suppose this response is referring to the JWT problem, correct @agent-Y? I tried it with 2.2.8 and I still get |
Interesting @iamrizwan077, I tried passing the |
If it helps anyone, I recently abandoned this whole login flow and instead started using the Google Identity API with one-tap sign-on. It's a better user experience and is way easier to implement because it removes the complicated Oauth, redirect, It feels like the way this "should" be done. The only downside I can see is that it removes Django allauth from the whole process, so if you need to support multiple social providers (Google, Facebook, etc.) it might make things more complicated. On the flip side, if you just need Google you can skip all the allauth overhead, extra tables, etc., and instead register users/retrieve details using your standard workflow. More details here: https://developers.google.com/identity/gsi/web/guides/overview |
@duplxey Here is the relevant frontend code that I used:
The given Google Client library returns me a credential (jwt access token), client_id and select_by attribute. I send that credential to dj-rest-auth endpoint with key 'access_token' and it works. If I send any other key, I get error in console 'access_token expected'..
Since I am using Django Tokens for auth, so I am sending a further request to my custom URL for retrieving Django token from database based on user data sent through that credential.. |
Sorry. My explanation is inadequate. |
I had the same problem then I switched to |
I had the same problem,
This solution works with dj-rest-auth==4.0.1 |
Downgrading to 0.50.0 worked for me, hoping this is fixed soon in newer releases |
You're the best! Thanks! This worked for me! |
I can confirm that this is working, i use reactjs-social-login==2.6.2 to obtain the access_code from google
this is the response i get from reactjs-social-login
my backend looks like this, no config needed:
Hope it will help someone! |
More information on the issueAs of Aug, 08, 2023, with django-allauth latest version 0.54.0 it seems to have a bug that results in function being unable to pass user identity to other funcitons, resulting in a frustrating code breake. If you face this error it can be worked around by a downgrade, Environment Information for reproducing this error: Error reproductionTrying to hook oauth using a simple general
Calling api
The above exception (not enough values to unpack (expected 2, got 1)) was the direct cause of the following exception: /opt/venv//lib/python3.11/site-packages/allauth/socialaccount/providers/google/views.py, line 42, in complete_login*
Class handeling this in version 0.50.0 seems to work fine:
The changes between the versions seem to reflect a transition from a simple OAuth 2.0 flow (where user data is fetched from the provider) to an OpenID Connect flow (where user identity information is embedded in the id_token). The OpenID Connect is an extension of OAuth 2.0 and provides richer identity features. However, as with any update, there might be some changes in the library's internal handling that could lead to the issue. As there seems to be a bug or unexpected behavior in the newer version that resulted in the "not enough values to unpack" error, downgrading to the older version that doesn't have this issue is a reasonable short-term fix. Always good to monitor the library for any patches or updates that address the issue in future versions. I hope this detailed report helps others facing similar issues and aids in the quicker resolution of this bug. Thank you to the community and maintainers for their continuous efforts in maintaining and improving this library. |
I was working through @duplxey's excellent tutorial article on Django REST Framework Authentication with Auth.js, and ran into this issue. I wanted to note that duplexy has two similar example apps:
The new app uses:
|
@DerekHill thanks for that, this allauth <--> dj-rest-auth gave me some awful headache |
While trying to implement the previous guide (https://testdriven.io/blog/django-rest-authjs/), my team also faced some of the issues described above:
|
@ap-pjgr, it works if you pass both See this piece of code ( dj-rest-auth/dj_rest_auth/registration/serializers.py Lines 100 to 154 in 23f097c
|
Thanks @DerekHill for the tip. I took the latest version of @duplxey, and only with these specific versions the thing worked, and now i'm able to login with google authorization. btw it works also with the latest django, not only 4.23 as mentioned in his requirements.txt . |
Thank you so much ❤️ |
Yes, you can use the Google OAuth 2.0 Playground to get your ID Token and Access Token. This post can help you: https://duizendstra.medium.com/how-to-easily-obtain-a-google-id-token-f1cde61541f0 |
Confirmed to work on the following version combination by passing both
Was this a bug before or is it a bug now? They are sparse, but any writeup I've read on this topic passes Honestly, the whole id/access token is a bit opaque. Perhaps I needs to read some more in-depth discussions on this authentication architecture. |
Yea this worked for me. Specifically, the following: from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = "http://127.0.0.1:3000/"
client_class = OAuth2Client
def post(self, request, *args, **kwargs):
print(f"Request data: {request.data}")
request.data["id_token"] = request.data.get("access_token")
return super().post(request, *args, **kwargs) Seems like a bug. |
The root cause of this issue is that starting from 0.52.0, django-allauth is using the ID token for extracting user information. The ID token is handed over together with the access token as part of the Google OAuth handshake, so with just the django-allauth scope in mind there is no need to make additional calls to fetch user information. However, in a broader scope, this does break the dj-rest-auth use case, causing this issue. In order to get this issue resolved, django-allauth has been changed to make a call to the userinfo endpoint in case no ID token is present. This change landed on version 0.61.0. However, dj-rest-auth is currently using this for its dependencies:
Given the amount of people impacted by this, I also backported that change to version 0.57.1 so that there is a version of dj-rest-auth that is compatible with a version of django-allauth containing the fix. This version has just been released. I do hope that current limitation of Hope this helps! |
Thanks to @pennersr the maintainer of django-allauth using a significant time to investigate this long term issue on our backend/app and push this to a solution that will hopefully benefit all users of django-allauth and dj-rest-auth. Forever grateful! |
I was getting an issue with this too. What was happening was: when the frontend sends the access_token to the GoogleLogin endpoin as "token" newer dj-rest-auth versions was causing the response to be just {"key":"xxxxxxxxxxxxxxxxxxx"}. my frontend was expecting the older format "access_token" "refresh_token" and user object. not "key". I didnt even know what to do with the key value. So I downgraded to allauth 0.50 and dj-rest-auth 2.2.6 and it is working fine again. |
I've been chasing this issue for the last few hours with the latest versions of Allauth (0.61.1) and dj-rest-auth (5.0.2) and by debugging both libraries, the underlying issue was being caused by the exception described here - jpadilla/pyjwt#814, where when the OAuth2 token was attempted to be used within ms of being granted, it would fail. As suggested in that thread, re-syncing my Windows clock actually fixed it. So throwing this here in case it's helpful to anyone else in the future. PS: Thanks again to pennersr for everything you do! |
So update to the above. Even with re-syncing my Windows clock every few hours, this is still pretty flakey and is still failing about 50% of the time. It seems even a delta of a few MS will throw this off. So this is what I came up with, and it seems to be much more reliable (even manually setting my local time back by a minute and increasing the allowed class GoogleOAuth2IatValidationAdapter(GoogleOAuth2Adapter):
def complete_login(self, request, app, token, response, **kwargs):
try:
delta_time = (
jwt.decode(
response.get("id_token"),
options={"verify_signature": False},
algorithms=["RS256"],
)["iat"]
- time()
)
except jwt.PyJWTError as e:
raise OAuth2Error("Invalid id_token during 'iat' validation") from e
except KeyError as e:
raise OAuth2Error("Failed to get 'iat' from id_token") from e
# Or change 30 to whatever you feel is a maximum amount of time you are willing to wait
if delta_time > 0 and delta_time <= 30:
sleep(delta_time)
return super().complete_login(request, app, token, response, **kwargs)
class GoogleLoginView(SocialLoginView):
adapter_class = GoogleOAuth2IatValidationAdapter
... The JWT is still fully validated as part of the |
What exactly is the resolution for this? I am also getting the ""Failed to exchange code for access token" error and I don't know why. There at least has to be better debug output for this, and it should be fixed. |
It worked for me, thank you very much. |
|
The problem is with django-allauth versions ,the newer versions seems to have a bug arround id_token, I downgraded django-allauth==0.62.1 to django-allauth==0.57.0 and this worked with django-rest-auth==6.0.0 |
Hello, How to solve "Failed to exchange code for access token" When sending {"code": "code from google"} on postman ? |
I'm using
dj-rest-auth
on the backend and NextAuth.js on the frontend. After successfully logging in via NextAuth.js I get the followingaccount
passed to mysignIn
callback:access_token
is a random string of characters. Not decodable by jwt.io.id_token
is a JWT token (header.payload.signature
). It is decodable by jwt.io.I then forward the
access_token
andid_token
to mydj-rest-auth
Google endpoint defined like this:I use the following request code:
This request fails with an error saying:
After some testing I figured out that
response["id_token"]
incomplete_login
returns the request'saccess_token
and not theid_token
.Sending your
id_token
as theaccess_token
without providingid_token
seems to work:Is this the expected behaviour? Am I missing something?
The text was updated successfully, but these errors were encountered: