Authorization backend function do_auth called twice
Opened this issue · 1 comments
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:
The very time user logs in - new entry in Users & User social auths tables created.
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:
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
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)