Skip to content
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

Authorization backend function do_auth called twice #186

Open
grindnoise opened this issue Jun 1, 2023 · 1 comment
Open

Authorization backend function do_auth called twice #186

grindnoise opened this issue Jun 1, 2023 · 1 comment

Comments

@grindnoise
Copy link

grindnoise commented Jun 1, 2023

I've implemented apple sign-in following this article using subclass:
https://github.com/truffls/sign-in-with-apple-using-django/blob/master/backend.md

Code:

import jwt
import requests
from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from social_core.utils import handle_http_errors
from social_core.backends.oauth import BaseOAuth2


class AppleOAuth2(BaseOAuth2):
    name = 'apple'
    ACCESS_TOKEN_URL = 'https://appleid.apple.com/auth/token'
    SCOPE_SEPARATOR = ','
    ID_KEY = 'uid'

    @handle_http_errors
    def do_auth(self, access_token, *args, **kwargs):
        response_data = {}
        client_id, client_secret = self.get_key_and_secret()
        
        headers = {'content-type': "application/x-www-form-urlencoded"}
        data = {
            'client_id': client_id,
            'client_secret': client_secret,
            'code': access_token,
            'grant_type': 'authorization_code',
        }

        res = requests.post(AppleOAuth2.ACCESS_TOKEN_URL, data=data, headers=headers)
        response_dict = res.json()
        id_token = response_dict.get('id_token', None)

        if id_token:
            decoded = jwt.decode(id_token, '', algorithms=["ES256"], options={"verify_signature": False})
            response_data.update({'email': decoded['email']}) if 'email' in decoded else None
            response_data.update({'uid': decoded['sub']}) if 'sub' in decoded else None

        response = kwargs.get('response') or {}
        response.update(response_data)
        response.update({'access_token': access_token}) if 'access_token' not in response else None

        kwargs.update({'response': response, 'backend': self})
        return self.strategy.authenticate(*args, **kwargs)

    def get_user_details(self, response):
        email = response.get('email', None)
        details = {
            'email': email,
        }

        return details

    def get_key_and_secret(self):
        headers = {
            'kid': settings.SOCIAL_AUTH_APPLE_ID_KEY
        }**strong text**

        payload = {
            'iss': settings.SOCIAL_AUTH_APPLE_ID_TEAM,
            'iat': timezone.now(),
            'exp': timezone.now() + timedelta(days=180),
            'aud': 'https://appleid.apple.com',
            'sub': settings.SOCIAL_AUTH_APPLE_ID_CLIENT,
        }

        client_secret = jwt.encode(
            payload,
            settings.SOCIAL_AUTH_APPLE_ID_SECRET,
            algorithm='ES256',
            headers=headers
        )
        
        return settings.SOCIAL_AUTH_APPLE_ID_CLIENT, client_secret

Authorization succeeds during first call, i receive access token from apple, new entries in Users & User social auths tables are created but after that function do_auth is called one more time causing duplicate entry:
enter image description here

The very time user logs in - new entry in Users & User social auths tables created.
enter image description here

During the second call id_token is empty because apple's authorization code is one time token.

in oauth2_endpoints.py after user was created it searches for access token and doesn't find any thus calls again:

enter image description here

Access token isn't created during the first iteration, I can't understand why. During the second call it again creates user, social auth user and this time access & refresh tokens are created. So every time I perform sign in via above auth backend new user is created.
Can somebody help me? My head is gonna blow up

@aguirredaniel
Copy link

I have the same problem in the covert_token flow.
At the moment I solved it by implementing some kind of cache to be able to query the id_token in the second call, in the following example I use a static dictionary as cache.

from social_core.backends.apple import AppleIdAuth
from social_core.utils import handle_http_errors


class AppleOAuth2(AppleIdAuth):
    name = 'apple'
    cache = {}

    @handle_http_errors
    def _auth_complete(self, access_token):
        data = self.auth_complete_params()
        data.update({'code': access_token})
        response = self.request_access_token(
            self.access_token_url(),
            data=data,
            headers=self.auth_headers(),
            auth=self.auth_complete_credentials(),
            method=self.ACCESS_TOKEN_METHOD
        )
        self.process_error(response)
        return response

    def do_auth(self, access_token, *args, **kwargs):
        response = kwargs.pop('response', None) or {}
        jwt_string = response.get(self.TOKEN_KEY) or AppleOAuth2.cache.pop(access_token, '')

        if not jwt_string:
            response = self._auth_complete(access_token, *args, **kwargs)
            jwt_string = response.get(self.TOKEN_KEY)
            AppleOAuth2.cache.update({access_token: jwt_string})
        else:
            response = {self.TOKEN_KEY: jwt_string}

        return super().do_auth(access_token, response=response, *args, **kwargs)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants