Skip to content

Instantly share code, notes, and snippets.

@montasaurus
Created September 24, 2024 17:29
Show Gist options
  • Save montasaurus/6376b15f334d93262190a3de50fd0716 to your computer and use it in GitHub Desktop.
Save montasaurus/6376b15f334d93262190a3de50fd0716 to your computer and use it in GitHub Desktop.
Parse and validate a Sign In with Apple ID JWT token in Python. Works as a CLI to test tokens with `uv run appleid_auth.py`
# /// script
# dependencies = [
# "httpx",
# "pyjwt[crypto]",
# ]
# ///
from dataclasses import dataclass
from datetime import timedelta
from typing import Any
import httpx
import jwt
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from jwt.algorithms import RSAAlgorithm
APPLE_PUBLIC_KEY_URL = "https://appleid.apple.com/auth/keys"
APPLE_ISSUER = "https://appleid.apple.com"
@dataclass
class ApplePublicKey:
key_id: str
public_key: RSAPublicKey
algorithm: str
# You could cache this, invalidating when you see a key id you don't recognize
def fetch_apple_public_keys() -> dict[str, ApplePublicKey]:
key_payload = httpx.get(APPLE_PUBLIC_KEY_URL).json()
keys = key_payload["keys"]
key_dict = {
key["kid"]: ApplePublicKey(
key_id=key["kid"],
public_key=RSAAlgorithm.from_jwk(key), # type: ignore
algorithm=key["alg"],
)
for key in keys
}
return key_dict
def decode_apple_user_token(
*,
apple_user_token: str,
apple_app_id: str | None,
options: dict[str, Any] | None = None,
):
if apple_app_id is None:
options = options or {}
options["verify_aud"] = False
public_keys = fetch_apple_public_keys()
unverified_header = jwt.get_unverified_header(apple_user_token)
key_id = unverified_header["kid"]
apple_public_key = public_keys.get(key_id)
if apple_public_key is None:
raise ValueError(
"Can't find an Apple public key for the key ID provided in the token header"
)
# This will throw exceptions e.g. jwt.exceptions.ExpiredSignatureError and jwt.exceptions.InvalidAudienceError
token = jwt.decode(
jwt=apple_user_token,
key=apple_public_key.public_key,
audience=apple_app_id,
issuer=APPLE_ISSUER,
algorithms=[apple_public_key.algorithm],
leeway=timedelta(minutes=5),
options=options,
)
return token
if __name__ == "__main__":
apple_user_token = input("User's identity token JWT: ")
apple_app_id = (
input(
"Your Apple app ID (e.g. com.example.app) [leave blank to skip verification]: "
)
or None
)
verify_timestamps = input("Verify timestamps? [Y/n]: ") or "Y"
options = None
if verify_timestamps.upper() == "N":
options = {"verify_exp": False, "verify_iat": False}
decoded_token = decode_apple_user_token(
apple_user_token=apple_user_token,
apple_app_id=apple_app_id,
options=options,
)
print() # noqa: T201
print(f"Decoded token with user id {decoded_token['sub']}\n") # noqa: T201
print(decoded_token) # noqa: T201
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment