JWT Token ========================= A wrapper around `PyJWT `_ that simplifies encoding, decoding, and validation of JSON Web Tokens. Quick Start ----------------------------------------------------------------------------------- .. code-block:: python from core_auth import JwtToken client = JwtToken(private_key="S3cr3t") # Encode token = client.encode(subject="user-123") # Decode payload = client.decode(token) print(payload["sub"]) # "user-123" print(payload["exp"]) # expiry timestamp print(payload["iat"]) # issued-at timestamp Custom Claims & Expiry ----------------------------------------------------------------------------------- Any additional JWT claims can be passed via ``claims``. The token lifetime is controlled by ``expire`` (seconds, default ``3600``). .. code-block:: python from core_auth import JwtToken client = JwtToken(private_key="S3cr3t", expire=7200) token = client.encode( subject="user-123", claims={ "iss": "my-auth-service", "aud": "my-api", "role": "admin", }, ) payload = client.decode(token, audience="my-api", issuer="my-auth-service") print(payload["role"]) # "admin" Decode Options ----------------------------------------------------------------------------------- **Audience and issuer validation** .. code-block:: python from core_auth import JwtToken, JwtException client = JwtToken(private_key="S3cr3t") token = client.encode(subject="user-123", claims={"iss": "auth", "aud": "api"}) # Validates that iss == "auth" and aud == "api" payload = client.decode(token, issuer="auth", audience="api") print(payload) # Raises JwtException if either claim does not match try: client.decode(token, issuer="wrong") except JwtException as exc: print(exc) # "Invalid token." **Expiry leeway** Allow a small clock-skew grace period when validating ``exp``: .. code-block:: python from datetime import timedelta from core_auth import JwtToken client = JwtToken(private_key="S3cr3t") token = client.encode(subject="user-123") payload = client.decode(token, leeway=timedelta(seconds=30)) **Skip signature verification** Useful for inspecting a token's claims without validating the signature (e.g. in tests or debugging): .. code-block:: python from core_auth import JwtToken client = JwtToken(private_key="S3cr3t") token = client.encode(subject="user-123") payload = client.decode(token, options={"verify_signature": False}) **Full decode** — returns header, payload, and raw signature together: .. code-block:: python from core_auth import JwtToken client = JwtToken(private_key="S3cr3t") token = client.encode(subject="user-123") result = client.decode(token, full_decode=True) print(result["header"]) # {'alg': 'HS256', 'typ': 'JWT'} print(result["payload"]) # {'exp': ..., 'iat': ..., 'sub': 'user-123'} print(result["signature"]) # b'...' Custom Headers ----------------------------------------------------------------------------------- Additional JOSE header fields (e.g. ``kid`` for key ID rotation) can be injected via ``headers``: .. code-block:: python from core_auth import JwtToken client = JwtToken(private_key="S3cr3t") token = client.encode(subject="user-123", headers={"kid": "key-v2"}) result = client.decode(token, full_decode=True) print(result["header"]["kid"]) # "key-v2" Authorization Header Parsing ----------------------------------------------------------------------------------- Extract the Bearer token from an HTTP ``Authorization`` header: .. code-block:: python from core_auth import JwtToken, JwtException client = JwtToken(private_key="S3cr3t") # Build a header from an encoded token token = client.encode(subject="user-123") auth_header = f"Bearer {token}" # Extract and decode try: raw_token = JwtToken.from_auth_header(auth_header) payload = client.decode(raw_token) print(payload["sub"]) # "user-123" except JwtException as exc: print(exc) # Malformed header raises JwtException try: JwtToken.from_auth_header("Token xyz") except JwtException as exc: print(exc) # "Bad format in Authorization header. Must be: Bearer " The header must follow the ``Bearer `` format exactly; anything else raises ``JwtException``. Asymmetric Keys (RSA / ECDSA / EdDSA) ----------------------------------------------------------------------------------- For asymmetric algorithms pass both ``private_key`` (for signing) and ``public_key`` (for verification). Keys can be PEM strings, PEM bytes, or ``cryptography`` key objects. **RSA — RS256** .. code-block:: python from core_auth import JwtToken, ALGORITHM private_pem = open("tests/resources/private.pem").read() public_pem = open("tests/resources/public.pem").read() client = JwtToken(private_key=private_pem, public_key=public_pem) token = client.encode(subject="user-123", algorithm=ALGORITHM.RS256) payload = client.decode(token, algorithms=[ALGORITHM.RS256]) print(payload["sub"]) # "user-123" **ECDSA — ES256** .. code-block:: python from cryptography.hazmat.primitives.asymmetric import ec from core_auth import JwtToken, ALGORITHM private_key = ec.generate_private_key(ec.SECP256R1()) client = JwtToken(private_key=private_key, public_key=private_key.public_key()) token = client.encode(subject="user-123", algorithm=ALGORITHM.ES256) payload = client.decode(token, algorithms=[ALGORITHM.ES256]) **EdDSA — Ed25519 / Ed448** .. code-block:: python from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from core_auth import JwtToken, ALGORITHM private_key = Ed25519PrivateKey.generate() client = JwtToken(private_key=private_key, public_key=private_key.public_key()) token = client.encode(subject="user-123", algorithm=ALGORITHM.EdDSA) payload = client.decode(token, algorithms=[ALGORITHM.EdDSA]) Using ``cryptography`` key objects directly: .. code-block:: python from cryptography.hazmat.primitives.asymmetric import rsa from core_auth import JwtToken, ALGORITHM private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) client = JwtToken(private_key=private_key, public_key=private_key.public_key()) token = client.encode(subject="user-123", algorithm=ALGORITHM.RS256) payload = client.decode(token, algorithms=[ALGORITHM.RS256]) Error Handling ----------------------------------------------------------------------------------- All errors raised by ``JwtToken`` are instances of ``JwtException``, which is itself a subclass of ``PyJWTError``: .. code-block:: python from core_auth import JwtToken, JwtException client = JwtToken(private_key="S3cr3t") try: payload = client.decode("an.invalid.token") except JwtException as exc: print(exc) # "Invalid token." # Expired token expired_client = JwtToken(private_key="S3cr3t", expire=-1) token = expired_client.encode(subject="user-123") try: expired_client.decode(token) except JwtException as exc: print(exc) # "Signature expired." API Reference ----------------------------------------------------------------------------------- .. automodule:: core_auth.auth.jwt_token.jwt_auth :members: :undoc-members: :show-inheritance: :private-members: Algorithms ----------------------------------------------------------------------------------- .. automodule:: core_auth.auth.jwt_token.algorithm :members: :undoc-members: :show-inheritance: :private-members: