Python-social-auth not getting valid Google OAuth2 data

I want to log in using python-social-auth function to log in to Google Plus in Django. When I log in from my site, everything works fine, and the correct data is added to the database.

However, I want to authenticate from my Android app as well. The user logs in to the application, which then sends the access token to the django API, which processes the login process to the following code, adapted from the documentation :

 @csrf_exempt @serengeti_api_request @psa('social:complete') def login_social_token(request, backend): # Ensure the token has been specified. token = request.META.get('HTTP_ACCESSTOKEN') if token is None: raise SerengetiApiRequestException('Access token is missing!') # Login the user for this session user = request.backend.do_auth(token) if user is None: raise SerengetiApiRequestException('Could not authenticate user!') login(request, user) # Store the email address if one has been specified (eg Twitter) email = request.META.get('HTTP_EMAIL') if email is not None: user.email = email user.save() # Prepare the parameters to be returned response = dict({ 'id': user.id, 'first_name': user.first_name, 'last_name': user.last_name, 'api_key': request.session.session_key, }) # Return a 200 status code to signal success. return HttpResponse(json.dumps(response, indent=4), status=200) 

When you log in from the website, the social_auth_usersocialauth table contains:

 id | provider | uid | extra_data ========================================== 10 | google-oauth2 | <myemail> | {"token_type": "Bearer", "access_token": "<token>", "expires": 3600} 

However, when you log out of the application using the above function, the operation completes normally, but the table entry looks like this:

 id | provider | uid | extra_data ========================================= 10 | google-oauth2 | <empty> | {"access_token": "", "expires": null} 

In addition, the auth_user table contains username as eeed494412obfuscated48bc47dd9b instead of the Google Plus username, and the email field is empty.

What am I doing wrong and how can I get the same functionality as on the website?

I would like to mention that I implemented Facebook and Twitter authentication from an Android application that call the above function and save the correct data, only problems with Google Plus create problems.

+5
source share
4 answers

I finally figured it out myself. According to this article in the Google Android documentation , I also need to request the plus.profile.emails.read scope when executing the request in an Android application. As soon as I added this, python-social-auth code was able to correctly store the email in uid fields. This allows him to recognize the same user, whether logging in from a website or application, and this is what I need. Here's the line of areas I'm using:

 String scopes = "oauth2:" + Plus.SCOPE_PLUS_LOGIN + " https://www.googleapis.com/auth/plus.profile.emails.read"; 

However, the extra_data field still contains the values ​​mentioned above. I believe that this is due to the need for an offline access request, which would allow Google Plus to transfer the remaining fields back to python-django-auth . More information can be found here .

+1
source

I have a project (not really working) with google authentication oauth2. I leave my configuration file here, so it may be useful to you (I used only oauth2, so some things can change):

 AUTHENTICATION_BACKENDS = ( 'social.backends.google.GoogleOAuth2', # /google-oauth2 'django.contrib.auth.backends.ModelBackend', ) SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your google oauth 2 key' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your secret google oauth 2 key' SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.associate_by_email', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) 

I am also attaching a view (note that I am using the django rest framework).

 class ObtainAuthToken(APIView): permission_classes = (permissions.AllowAny,) serializer_class = AuthTokenSerializer model = Token # Accept backend as a parameter and 'auth' for a login / pass def post(self, request, backend): if backend == 'auth': # For admin purposes serializer = self.serializer_class(data=request.DATA) if serializer.is_valid(): token, created = Token.objects.get_or_create(user=serializer.object['user']) return Response({'token': token.key}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: # Here we call PSA to authenticate like we would if we used PSA on server side. user = register_by_access_token(request, backend) # If user is active we get or create the REST token and send it back with user data if user and user.is_active: token, created = Token.objects.get_or_create(user=user) return Response({'id': user.id, 'name': user.username, 'token': token.key}) else: return Response("Bad Credentials, check the Access Token and/or the UID", status=403) @strategy('social:complete') def register_by_access_token(request, backend): # This view expects an access_token GET parameter token = request.GET.get('access_token') backend = request.strategy.backend user = backend.do_auth(access_token=token, backend=backend) if user: # login(request, user) #Only useful for web.. return user else: return None 

and in urls.py:

 urlpatterns = patterns( '', url(r'^login/(?P<backend>[\w-]+)$', ObtainAuthToken.as_view(), ), ) 

Sorry for attaching all this code and not providing a specific answer, but more data is needed because the error can come from many sources (bad api keys, bad settings configuration, pipeline ..). I hope the code helps.

+3
source

Just wanted to share an alternative way of doing this. This example is quite primitive and does not cover all cases (for example, failed authentication). However, it should provide enough information on how OAuth2 authentication can be performed.

Get CLIENT ID

Obtain the customer ID from your OAuth2 service provider (such as Google) and configure the forwarding URLs.

I assume that you have already done this.

Create a login / registration link

You need to create a login / registration link in your view. It should be something like this:

 https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{REDIRECT_URL}}&scope=email 

Replace the {{CLIENT_ID}} and {{REDIRECT_URL}} information obtained in the previous step.

Create a new view

In urls.py add something like:

 url(r'^oauth2/google/$', views.oauth2_google), 

In your views.py create a method:

 def oauth2_google(request): # Get the code after a successful signing # Note: this does not cover the case when authentication fails CODE = request.GET['code'] CLIENT_ID = 'xxxxx.apps.googleusercontent.com' # Edit this CLIENT_SECRET = 'xxxxx' # Edit this REDIRECT_URL = 'http://localhost:8000/oauth2/google' # Edit this if CODE is not None: payload = { 'grant_type': 'authorization_code', 'code': CODE, 'redirect_uri': REDIRECT_URL, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET } token_details_request = requests.post('https://accounts.google.com/o/oauth2/token', data=payload) token_details = token_details_request.json() id_token = token_details['id_token'] access_token = token_details['access_token'] # Retrieve the unique identifier for the social media account decoded = jwt.decode(id_token, verify=False) oauth_identifier = decoded['sub'] # Retrieve other account details account_details_request = requests.get('https://www.googleapis.com/plus/v1/people/me?access_token=' + access_token) account_details = account_details_request.json() avatar = account_details['image']['url'] # Check if the user already has an account with us try: profile = Profile.objects.get(oauth_identifier=oauth_identifier) profile.avatar = avatar profile.save() user = profile.user except Profile.DoesNotExist: user = User.objects.create_user() user.save() profile = Profile(user=user, oauth_identifier=oauth_identifier, avatar=avatar) profile.save() user.backend = 'django.contrib.auth.backends.ModelBackend' login(request, user) return redirect('/') 

You may need the following imports:

 from django.shortcuts import redirect import jwt # PyJWT==0.4.1 import requests # requests==2.5.0 import json 
+3
source

I ran into the same problem. The reason the extra fields in your Google user is not set is because python-social-auth calls the google server to install these things, but if you only call Google with access_token, this will not be enough to get Google returns refresh_token and all other fields associated with it. You can hack it by installing them manually, but then you will get the same access and update tokens as the client. Google recommends using the client to create a new authorization token with any areas that you need, and then send this authentication token to the server, which will then turn it into an access and update token. See here for details (this is a bit related to reading): https://developers.google.com/identity/protocols/CrossClientAuth

If you really intend to do this as part of what python-social-auth does, I would recommend creating your own authentication server, name it GoogleOAuth2AuthorizationCodeAuth ( see more details here ).

An easier and probably easier to use and rough way is to publish the access_token on my server to log in as a google user (which you do correctly, it seems), and then later get another authorization token from the client to send to a separate final point, after which I will handle the call to another Credentials model object that is connected to the user profile.

In DjangoRestFramework:

 class GoogleAuthorizationCodeView(APIView): def post(self, request, format=None): credentials = flow.step2_exchange(code) saved_creds = GoogleCredentials.objects.create(credentials=credentials) return Response(status=status.HTTP_201_CREATED) 
0
source

Source: https://habr.com/ru/post/1212276/


All Articles