Closed Altroo closed 1 year ago
PRs welcome. Please pin your version in your manifest and test before upgrading versions.
PRs welcome. Please pin your version in your manifest and test before upgrading versions.
requirements.txt
Django==4.1.5 djangorestframework==3.14.0 django-allauth==0.52.0 dj-rest-auth==2.2.6
settings.py :
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
],
# in order to receive a refresh token on first login and on reauthentication requests
# (which is needed to refresh authentication tokens in the background,
# without involving the user’s browser)
'AUTH_PARAMS': {
'access_type': "offline",
},
'APP': {
'client_id': config('GOOGLE_CLIENT_ID'),
'secret': config('GOOGLE_SECRET'),
},
}
}
SOCIALACCOUNT_ADAPTER="account.adapters.BaseSocialAccountAdapter"
SOCIALACCOUNT_STORE_TOKENS=True
SOCIALACCOUNT_QUERY_EMAIL=True
SOCIALACCOUNT_EMAIL_VERIFICATION=False
SOCIALACCOUNT_AUTO_SIGNUP=True
ACCOUNT_USERNAME_REQUIRED=False
ACCOUNT_AUTHENTICATION_METHOD="email"
ACCOUNT_EMAIL_REQUIRED=True
ACCOUNT_EMAIL_VERIFICATION="none"
ACCOUNT_MAX_EMAIL_ADDRESSES=1
BaseSocialAccountAdapter
from allauth.account.utils import perform_login
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
import logging
from account.models import CustomUser
logger = logging.getLogger(__name__)
class BaseSocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
user = sociallogin.user
if user.id:
return
try:
customer = CustomUser.objects.get(email=user.email)
sociallogin.state['process'] = 'connect'
perform_login(request, customer, 'none')
except CustomUser.DoesNotExist:
pass
def get_app(self, request, provider, config=None):
# NOTE: Avoid loading models at top due to registry boot...
from allauth.socialaccount.models import SocialApp
# 1 added line here
from allauth.socialaccount import app_settings
config = config or app_settings.PROVIDERS.get(provider, {}).get('APP')
if config:
app = SocialApp.objects.get_or_create(provider=provider)[0]
for field in ["client_id", "secret", "key", "certificate_key"]:
setattr(app, field, config.get(field))
else:
app = SocialApp.objects.get_current(provider, request)
return app
def authentication_error(self, request, provider_id, error=None, exception=None, extra_context=None):
logger.warning('Facebook error! - provider id : {} - error : {} - exception : {} - extra_context : {}'
.format(provider_id, error.__str__(), exception.__str__(), extra_context))
same exact issue, this made me waste hours of debugging, please provide a solution so we could use it. I will try installing an old version
same exact issue, this made me waste hours of debugging, please provide a solution so we could use it. I will try installing an old version
I think the issue isn't with the library, but maybe google api changed the response data.
installing an older version didn't work, is there a workaround for it? or at least another method of using google sign in as an api
@NyllRE Could you please provide your config settings ?
Social Account + Account settings:
ACCOUNT_USERNAME_REQUIRED=True
# ACCOUNT_AUTHENTICATION_METHOD="email" #! this produces an error that stops the server
ACCOUNT_EMAIL_REQUIRED=False
ACCOUNT_EMAIL_VERIFICATION="none"
ACCOUNT_MAX_EMAIL_ADDRESSES=1
SOCIALACCOUNT_STORE_TOKENS=True
SOCIALACCOUNT_QUERY_EMAIL=True
SOCIALACCOUNT_EMAIL_VERIFICATION=False
SOCIALACCOUNT_AUTO_SIGNUP=True
social providers:
SOCIALACCOUNT_PROVIDERS = {
"google": {
# For each OAuth based provider, either add a ``SocialApp``
# (``socialaccount`` app) containing the required client
# credentials, or list them here:
"APP": {
"client_id": "***",
"secret": "***",
"key": ""
},
# These are provider-specific settings that can only be
# listed here:
"SCOPE": [
"profile",
"email",
],
"AUTH_PARAMS": {
"access_type": "offline",
}
}}
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:8000/accounts/google/login/callback/'
client_class = OAuth2Client
urls:
path('google/', GoogleLogin.as_view(), name='google_login'), # full url => auth/google/
when making a post request to auth/google/
:
@NyllRE It seems like you are having the same issue as me: https://github.com/iMerica/dj-rest-auth/issues/467 I'm not sure if our issue is the same issue that @Altroo is having but I could be wrong.
@Willem-Nieuwoudt I originally had the same error that @Altroo had but when I played around trying to fix the issue it then produced this other error, so I think they are based on the same under-laying issue
@NyllRE Alright. I personally haven't experienced @Altroo 's issue as of yet. Only that second issue you are facing now. Are you also only facing that second issue when posting an invalid token to the /auth/google endpoint? Everything works fine for me when I post a valid token, but it throws that error when I post the same token twice or any random invalid token. Not sure if you are doing this already but I have to decode the token that google gives me before posting it to /auth/google/. Im currently doing that with the decodeURIComponent('place_token_here') javascript function. It spits out a decoded version of the token. I then send that token to /auth/google/, which works for me.
const googleScript: any = document.createElement('script');
googleScript.setAttribute('src', 'https://accounts.google.com/gsi/client');
document.head.appendChild(googleScript);
googleAuth = () => {
// eslint-disable-next-line
const client = google.accounts.oauth2.initCodeClient({
client_id: '***',
scope: 'https://www.googleapis.com/auth/userinfo.profile',
ux_mode: 'popup',
callback: (response: { code: any }) => {
if (response.code) {
console.log(response.code); // I later tried to add the `decodeURIComponent()` function before logging the code, no difference
}
},
});
client.requestCode();
};
this logs the response code to the console
Oh okay looks like our situation is a little different then, because I don't even have a frontend set up at all at this stage. My process to get the token is how they explain it in the docs: https://dj-rest-auth.readthedocs.io/en/latest/installation.html#social-authentication-optional
I essentially just go to the following url to get the token: https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=CALLBACK_URL_YOU_SET_ON_GOOGLE&prompt=consent&response_type=code&client_id=YOUR_CLIENT_ID&scope=openid%20email%20profile&access_type=offline
Obviously I just replace the callback url and client_id with the relevant values. After that I just grab the token from the url and use that decodeURIComponent function on the token and post it to /auth/google. That has worked for me so far. Not so sure how the process would differ with a Vue frontend setup though.
my method basically does the same thing, I tried your method just to be sure and yea pretty much the same thing happens I still get the same error
Hi, this issue is caused by an update to the Django-allauth library. For easy and temporary fix you can downgrade it from 0.52 to 0.51 and it should work. Hope a fix will come out soon.
I downgraded and it didn't work, is there some sort of demo project that I can try to then compare what might be the reason I get this issue?
My response was to @Altroo issue... @NyllRE your issue is different I can try and help, Can you put your full urls file?
oh sorry I didn't realize. but thanks for your efforts, here's the file
base urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
...
path('auth/', include('authapp.urls')),
...
]
authapp/urls.py
from django.urls import path, include
from .views import *
from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView
from dj_rest_auth.registration.views import (
SocialAccountListView, SocialAccountDisconnectView
)
urlpatterns = [
path('', Home),
path('users/', viewUsers.as_view()),
path('lib/', include('dj_rest_auth.urls')),
path('lib/registration/', include('dj_rest_auth.registration.urls')),
path('facebook/', FacebookLogin.as_view(), name='fb_login'),
path('google/', GoogleLogin.as_view(), name='google_login'),
path('google/connect/', GoogleConnect.as_view(), name='google_connect'),
path(
'socialaccounts/',
SocialAccountListView.as_view(),
name='social_account_list'
),
path(
'socialaccounts/<int:pk>/disconnect/',
SocialAccountDisconnectView.as_view(),
name='social_account_disconnect'
),
path('token/', UserLogIn.as_view(), name='token_optain_more'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path('register/', RegisterView.as_view(), name='auth_register'),
]
views.py:
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.oauth2.views import OAuth2LoginView
...
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = 'http://127.0.0.1:8000/accounts/google/login/callback/'
client_class = OAuth2Client
pip freeze:
asgiref==3.6.0
certifi==2022.12.7
cffi==1.15.1
charset-normalizer==2.1.1
crispy-bootstrap5==0.7
cryptography==39.0.0
defusedxml==0.7.1
dj-rest-auth==2.2.4
Django==4.1.4
django-allauth==0.50.0
django-cors-headers==3.13.0
django-crispy-forms==1.14.0
django-rest-framework==0.1.0
djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2
idna==3.4
oauthlib==3.2.2
pycparser==2.21
PyJWT==2.6.0
python3-openid==3.2.0
pytz==2022.7
requests==2.28.1
requests-oauthlib==1.3.1
six==1.16.0
social-auth-app-django==5.0.0
social-auth-core==4.3.0
sqlparse==0.4.3
urllib3==1.26.13
tried
django-allauth
0.51 then tried 0.50 to see if there's any difference but no difference lol, I also tried olderdj-rest-auth
versions to see if it might be resolved with older versions but it still persists
I might have found something that could solve the issue,
I read the source code of django-allauth and found that the issue I was getting in here was because the access token was empty, apparently this code exists inside the source code:
allauth/socialaccount/providers/oauth2/client.py, line 91, in get_access_token:
if not access_token or "access_token" not in access_token:
raise OAuth2Error("Error retrieving access token: %s" % resp.content)
the issue is that the url that dj-rest-auth
documentation only returns the code but not the access token, but as you can see the code checks if there is an access token or if the string "access_token" in inside the access_token string. so I tried just adding "access_token" and it worked
after filling it in I got the original issue string indices must be integers
and surfed the code again, turns out the issue was because since me and the op only posted either the access token or the code, the response returned was a string instead of an object.
if you inspect the GoogleOAuth2Adapter
class you will find that the function complete_login
is trying to return identity data in this way
this apparently was the reason we got this error:
the route displayed on the error is different because I copied the function to modify it to get different results
so I tried to check the reason we were getting this error by printing the response value and type. turns out printing response
and type(response)
returns access_token <class 'str'>
which means that response was for some reason getting the access_token value and GoogleOAuth2Adapter
was trying to extract id_token
from that string
I dug deep to find the source of why it was giving a string to the response and found this
for some reason the comment writes OAuth1, this is on version 2.2.6. weird but I guess it might refer to something else
at this point I tried digging deeper but I found that it was getting too complicated for me at this point. I hope this could be enough to find the source of the issue
Could it be related to my issue in the upgrade of django-allauth from 0.51.0 to 0.52.0?
Issue: https://github.com/pennersr/django-allauth/issues/3228
Code owner believes the problem is related to a old issue in dj_rest_auth that now resurfaced.
@henningbra Yes, the main issue here is the same of what you provided.
@NyllRE You have a bit of a mix, I'll try to explain. There are 2 main types of Google oauth flows used here:
The access token method where your front will get the access token and Id token and send it to the backend to login/register. This is where access token is used and this is what got broken with the change in allauth 0.52.0 behavior.
The code method where you get only a code in your front end and the Django check it and verify it for you, making it a bit more secure (Some will disagree). The code is good only for one use and then it goes off.
The reason you get invalid grant "bad request" can be caused by many reasons from issues in your config of the Google app to clock setting issues. (See here: https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35)
My best guess is google app conf issues or that allauth is using your code to verify it and when you run it again it's already bad...
Try removing allauth urls ("/accounts/") and see if it changes - you will get an error because callback url won't work but take the code from there
Anyone have any update or luck with solving this?
Or at least narrowed down what the problem is? (On dj-rest-auth's side? Django-allauth's side? Google's API?)
I've tried reverting back a bunch of dj-rest-auth and django-allauth's but getting the same errors.
@wanglophile @alonw0 @NyllRE @henningbra @iMerica Fix : revert to those versions, tested it & it worked again. django-allauth==0.51.0 dj-rest-auth==2.2.5
The issue is in allauth to fix with last versions:
allauth/socialaccount/providers/google/views.py should be :
import requests
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
from .provider import GoogleProvider
class GoogleOAuth2Adapter(OAuth2Adapter):
provider_id = GoogleProvider.id
access_token_url = "https://accounts.google.com/o/oauth2/token"
authorize_url = "https://accounts.google.com/o/oauth2/auth"
profile_url = "https://www.googleapis.com/oauth2/v1/userinfo"
def complete_login(self, request, app, token, **kwargs):
resp = requests.get(
self.profile_url,
params={"access_token": token.token, "alt": "json"},
)
resp.raise_for_status()
extra_data = resp.json()
login = self.get_provider().sociallogin_from_response(request, extra_data)
return login
oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(GoogleOAuth2Adapter)
allauth/socialaccount/providers/google/provider.py should be :
...
class GoogleProvider(OAuth2Provider):
def extract_uid(self, data):
return str(data["id"]) // <--
...
Downgrading allauth to 0.51.0
solved it for me.
Why is the issue closed, will/should this be fixed in allauth and is there an issue for it to track?
Is this still an issue with allauth 0.53.1
?
Seems to be fixed, but have upgraded yet, can't confirm.
On Sun, 26 Mar 2023, 13:11 Toni Engelhardt, @.***> wrote:
Is this still an issue with allauth 0.53.1 ?
— Reply to this email directly, view it on GitHub https://github.com/iMerica/dj-rest-auth/issues/465#issuecomment-1484064002, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABL24SNB2HJAX3DU4FDJVILW6AXA3ANCNFSM6AAAAAATRALVQU . You are receiving this because you were mentioned.Message ID: @.***>
Trace :
site-packages/dj_rest_auth/registration/serializers.py, line 150, in validate
->
login = self.get_social_login(adapter, app, social_token, token)
site-packages/dj_rest_auth/registration/serializers.py, line 60, in get_social_login
->
social_login = adapter.complete_login(request, app, token, response=response)
site-packages/allauth/socialaccount/providers/google/views.py, line 23, in complete_login
try:
identity_data = jwt.decode(
->response["id_token"], …
Error :
TypeError : string indices must be integers, not 'str'
.The response is not a dict but instead it's a string of some id which then i tried and got :
OAuth2Error("Invalid id_token")