-
-
Save davidhariri/b053787aabc9a8a9cc0893244e1549fe to your computer and use it in GitHub Desktop.
import jwt | |
from jwt.algorithms import RSAAlgorithm | |
import requests | |
from time import time | |
import json | |
import os | |
APPLE_PUBLIC_KEY_URL = "https://appleid.apple.com/auth/keys" | |
APPLE_PUBLIC_KEY = None | |
APPLE_KEY_CACHE_EXP = 60 * 60 * 24 | |
APPLE_LAST_KEY_FETCH = 0 | |
class AppleUser(object): | |
def __init__(self, apple_id, email=None): | |
self.id = apple_id | |
self.email = email | |
self.full_user = False | |
if email is not None: | |
self.full_user = True | |
def __repr__(self): | |
return "<AppleUser {}>".format(self.id) | |
def _fetch_apple_public_key(): | |
# Check to see if the public key is unset or is stale before returning | |
global APPLE_LAST_KEY_FETCH | |
global APPLE_PUBLIC_KEY | |
if (APPLE_LAST_KEY_FETCH + APPLE_KEY_CACHE_EXP) < int(time()) or APPLE_PUBLIC_KEY is None: | |
key_payload = requests.get(APPLE_PUBLIC_KEY_URL).json() | |
APPLE_PUBLIC_KEY = RSAAlgorithm.from_jwk(json.dumps(key_payload["keys"][0])) | |
APPLE_LAST_KEY_FETCH = int(time()) | |
return APPLE_PUBLIC_KEY | |
def _decode_apple_user_token(apple_user_token): | |
public_key = _fetch_apple_public_key() | |
try: | |
token = jwt.decode(apple_user_token, public_key, audience=os.getenv("APPLE_APP_ID"), algorithm="RS256") | |
except jwt.exceptions.ExpiredSignatureError as e: | |
raise Exception("That token has expired") | |
except jwt.exceptions.InvalidAudienceError as e: | |
raise Exception("That token's audience did not match") | |
except Exception as e: | |
print(e) | |
raise Exception("An unexpected error occoured") | |
return token | |
def retrieve_user(user_token): | |
token = _decode_apple_user_token(user_token) | |
apple_user = AppleUser(token["sub"], token.get("email", None)) | |
return apple_user |
if index zero doesnot work for you, you can try index one
You should use the key that was used for encoding the payload (can be found in token's kid
header). @emcas88 has pasted an example above.
Thank you, was very helpful. This is what I used with python-jose[cryptography]
instead of old PyJWT
import logging
import requests
from typing import Dict, Union, List
from fastapi import HTTPException
from jose import jwt, jwk, JWTError
from starlette import status
from modules.database.models.member_user import MemberUser
from services.user_service.domain.providers.apple.constants import AppleUtilConstants
from settings.secrets import APPLE_OAUTH2_WEB_CLIENT_ID
def validate_user(provider_id_token_jwt: str) -> Dict[str, str]:
# Get public key set from Apple
try:
keys: Dict[str, List[Dict[str, str]]] = dict(requests.get(AppleUtilConstants.KEYS_URL.value).json())
except Exception as error:
logging.exception(repr(error))
raise Exception("Unable to connect to Apple.")
# Get headers of our id token
headers = jwt.get_unverified_headers(token=provider_id_token_jwt)
# Find matching public key based on the kid header
public_key: Dict[str, str] = next(filter(lambda key: (key["kid"] == headers["kid"]), keys["keys"]))
# Construct the actual public_key
public_rsa_key = jwk.construct(public_key_set)
try:
decoded_id_token = dict(
jwt.decode(
token=provider_id_token_jwt,
audience=APPLE_OAUTH2_WEB_CLIENT_ID,
key=public_key,
algorithms=["RS256"],
)
)
except jwt.ExpiredSignatureError as error:
logging.info(repr(error))
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Token has expired",
)
except JWTError as error:
logging.info(repr(error))
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Unable to process given id_token",
)
except Exception as error:
logging.exception(repr(error))
raise Exception("An unexpected error occurred")
return decoded_id_token
thank you, so match ! I loosed 2 days before found this solution !
This thread is a godsend
pyjwt >= 2.0 diff from pyjwt == 1.7.1 options={"verify_exp": False},
algorithms=["RS256"],
.. code:: python
import jwt
from jwt import PyJWKClient
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw"
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
data = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience="https://expenses-api",
options={"verify_exp": False},
)
print(data)
amazing thank you so much! this works, with a bit of tweaking for jose:
from jose import jwt, JWTError
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# method to validate apple sign in using id_token param
#....
try:
public_key_from_apple = _fetch_apple_public_key()
apple_public_key_as_string = public_key_from_apple.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
try:
decoded = jwt.decode(id_token_str, apple_public_key_as_string, algorithms="RS256", audience=os.getenv("APPLE_CLIENT_ID"))
except Exception as e:
raise Exception(e)
I hit signature verification issues since it's assuming the first key is the one used to sign the token. Wrote an updated version here that you can use as a CLI to test with as well: https://gist.github.com/montasaurus/6376b15f334d93262190a3de50fd0716
Thanks for this ... it daved me another sleepless night.
Inaddition the
I had to use for it to work
if index zero doesnot work for you, you can try index one