sign-in-with-apple-using-django icon indicating copy to clipboard operation
sign-in-with-apple-using-django copied to clipboard

Here is the updated code for backend.

Open srijan113 opened this issue 10 months ago • 0 comments

Here is the updated code for backend.

There were certain problems that I faced during this process. Unlike in docs I am not using any package for authentication. So, I had to strip down the code to bare minimum so below code is for creating client secret and validating the authorization_code.

Problem was with identity_token or authorization_code by client side - In docs, access_token was mentioned and all that client side can provide was identity_token or authorization_code or both. Here I have used authorization_code.

How to use PRIVATE_KEY securely(maybe not a part that docs is related to). I have stored PRIVATE_KEY in my env file. I have just appended the private key into a single line and later on strip to get raw PRIVATE_KEY

Example:
    APPLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nyourrandomnstring1\nyourrandomnstring2\nyourrandomnstring3\n-----END PRIVATE KEY-----

Original:
    -----BEGIN PRIVATE KEY-----
    yourrandomnstring1
    yourrandomnstring2
    yourrandomnstring3
    -----END PRIVATE KEY-----

Decoding of the id_token.During the time docs was made may be it was correct but the code mention in code didn't worked for me so I had to update the decoding processing.

PY JWT reading the claim without validation

```
decoded = jwt.decode(id_token, options={"verify_signature": False})
```

Things to remember:

  1. authorization_code is 1 time thing, create new one every time you try to login (client side).

  2. PRIVATE_KEY is 1 time thing once you download cannot be downloaded again

  3. Below code only creates client_secret and validates the authorization_code this is not a complete flow

  4. It only returns email if authorization_code is valid otherwise it returns empty dict.

import jwt
import requests
from datetime import timedelta
from medhavhilms.settings import env
from django.utils import timezone

APPLE_BUNDLE_NAME = env('APPLE_BUNDLE_NAME')
APPLE_KEY_ID = env('APPLE_KEY_ID')
APPLE_TEAM_ID = env('APPLE_TEAM_ID')
APPLE_BUNDLE_ID = env('APPLE_BUNDLE_ID')
APPLE_PRIVATE_KEY = env('APPLE_PRIVATE_KEY').strip().replace('\\n', '\n')  # Remove whitespace and replace escaped newlines

class AppleOAuth2():
    ACCESS_TOKEN_URL = 'https://appleid.apple.com/auth/token'

    def authenticate(self, auth_code):
        response_body = {}
        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': auth_code,
            '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, options={"verify_signature": False})
            response_body.update({'email': decoded.get('email')})
    
        return response_body
    
    def get_key_and_secret(self):
        headers = {
            'alg': 'ES256',
            'kid': APPLE_KEY_ID
        }

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

        client_secret = jwt.encode(
            payload, 
            APPLE_PRIVATE_KEY, 
            algorithm='ES256', 
            headers=headers
        )
        return APPLE_BUNDLE_ID, client_secret

srijan113 avatar Apr 02 '24 08:04 srijan113