python-social-auth not getting correct Google OAuth2 details

前端 未结 4 828

I want to login a user using the python-social-auth functionality for Google Plus signin in Django. When logging in from my website, everything works fine and the c

相关标签:
4条回答
  • 2021-02-08 19:15

    Just wanted to share an alternative way of doing this. This example is quite primitive and doesn't cover all cases (e.g. failed authentication). However, it should give enough insight into how OAuth2 authentication can be done.

    Obtain CLIENT ID

    Obtain a CLIENT ID from OAuth2 service provider (e.g. Google) and configure redirect URLs.

    I assume you have already done this.

    Create a login / registration link

    You need to generate 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 {{CLIENT_ID}} and {{REDIRECT_URL}} with the details you 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 might need the following imports:

    from django.shortcuts import redirect
    import jwt # PyJWT==0.4.1
    import requests # requests==2.5.0
    import json
    
    0 讨论(0)
  • 2021-02-08 19:24

    I have a project (not running actually) with google oauth2 authentication. I leave here my config file so it may be useful to you (I was only using oauth2 so some things may vary):

    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 attach the view also (note that I'm using 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 the 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.

    0 讨论(0)
  • 2021-02-08 19:29

    I finally figured it out myself. According to this article in the Android's Google Plus documentation, I also need to request the plus.profile.emails.read scope when making the request in the Android app. Once I added this, the python-social-auth code managed to store the email properly in the uid fields. This allows it to recognize the same user whether logging in from the website or the app, which is what I needed. Here's the scopes string I use:

    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 I mentioned above. I believe this is due to needing to request offline access as well, which would allow Google Plus to pass the missing fields back to python-django-auth. More details can be found here.

    0 讨论(0)
  • 2021-02-08 19:29

    I've been running into the same problem. The reason why the extra_fields on your google user isn't being set is because python-social-auth calls the google server to set those things, but if you're calling Google with just an access_token, it won't be enough to get Google to return the refresh_token and all those other auth related fields. You can hack it by setting them manually, but then you'd end up using the same access and refresh tokens as the client. Google recommends that you use the client to generate a new authorization token with whatever scopes you need, and then send that auth token to the server, which then will turn it into an access and refresh token. See here for the details (it's a bit of an involved read): https://developers.google.com/identity/protocols/CrossClientAuth

    If you're really committed to doing this in the scope of what python-social-auth does, I'd recommend making a custom auth backend, call it GoogleOAuth2AuthorizationCodeAuth (see here for details).

    The lazier and probably easy-to-break and gross way is to post the access_token to my server to sign in as a google user (which you're doing properly, it seems), and then later, get another authorization token from the client in order to post to a separate endpoint, which I'll then handle turning into another Credentials model object that's connected to a 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 讨论(0)
提交回复
热议问题