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: