JWT
Tip
The jam.jwt module is deprecated, but the
documentation is still available at /usage/jwt
Token modes¶
JWT supports three operating modes:
JWS-only (signed tokens)¶
Standard signed JWT. Payload is readable by anyone but cannot be tampered with.
jwt = JWT(alg="RS256", secret_key=private_key)
token = jwt.encode(sub="user", exp=3600)
data = jwt.decode(token)
JWE-only (encrypted tokens)¶
Encrypted-only token. Payload is confidential but not integrity-protected by signature.
jwt = JWT(enc="A256GCM", secret_key=encryption_key)
token = jwt.encrypt({"secret": "data"})
data = jwt.decrypt(token)
JWS+JWE (sign-then-encrypt, RFC 7519)¶
Hybrid mode: first signs the payload, then encrypts the signed token. Follows RFC 7519 nested JWT pattern.
jwt = JWT(alg="RS256", enc="A256GCM", secret_key=key)
token = jwt.encrypt({"data": "sensitive"}) # Signs then encrypts
data = jwt.decrypt(token) # Decrypts then verifies signature
Automatic JWE algorithm selection¶
When using JWS+JWE mode with a symmetric key, JWT automatically selects the JWE key management algorithm based on key type:
| Key type | Auto-selected JWE alg |
|---|---|
| RSA | RSA-OAEP |
| EC | ECDH-ES |
| Symmetric (>=32 bytes) | A256KW |
| Symmetric (<32 bytes) | A128KW |
For symmetric keys, an encryption key is derived from the signing key using
HKDF-SHA256 with salt jwe-encryption and info encryption-key.
Use in instance¶
Config¶
[jam.jose.jwt]
alg = "RS256"
secret = "$RSA_PRIVATE_KEY"
[jam.jose.jwt.list]
type = "black"
backend = "redis"
redis_uri = "redis://localhost:6379"
Args:
alg:str- JWT signing algorithm. Supports:HS256,HS384,HS512,RS256,RS384,RS512,ES256,ES384,ES512,PS256,PS384,PS512.secret:str- Secret key for token signing. Default reads fromJAM_JWT_SECRET_KEYenvironment variable.list:dict[str, Any] | None- Token list config. See: Lists.
Usage¶
from jam import Jam
jam = Jam(config="config.toml")
Encode token¶
Method: jam.jwt_encode
Args:
iss:str | None = None- Token issuer.sub:str | None = None- Token subject.aud:str | None = None- Token audience.exp:int | None = None- Token lifetime in seconds.nbf:int | None = None- Token not-before time in seconds.jti:str | None = None- Unique token ID. IfNone, auto-generated.header:dict[str, Any] | None = None- Additional header fields.payload:dict[str, Any] | None = None- Custom payload data.
Returns:
str: Encoded JWT.
token = jam.jwt_encode(
iss="YourService",
sub="user@mail.com",
exp=3600,
payload={"role": "admin"}
)
print(token)
>>> eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyQ...
Decode token¶
Method: jam.jwt_decode
Args:
token:str- JWT token.validate_claims:bool = True- Validate exp and nbf claims.
Returns:
dict[str, Any]: Decoded payload with header.
Raises:
JamJWTExpired- Token has expired.JamJWTNotYetValid- Token is not yet valid (nbf).JamJWSVerificationError- Invalid signature.
data = jam.jwt_decode(
token=token,
validate_claims=True
)
print(data)
>>> {
'header': {'alg': 'RS256', 'typ': 'JWT'},
'payload': {
'iss': 'YourService',
'sub': 'user@mail.com',
'exp': 1772132706,
'iat': 1772129106,
'jti': '0c2c38d2-5dcb-4294-bb2d-0820f6ff787d',
'role': 'admin'
}
}
Encrypt token¶
Method: jam.jwt_encrypt
Creates encrypted JWT (JWS+JWE or JWE only).
Args:
payload:dict[str, Any] | str- Data to encrypt.header:dict[str, Any] | None = None- Additional JWE header fields.
Returns:
str: Encrypted JWT.
encrypted = jam.jwt_encrypt(
payload={"user_id": 123, "role": "admin"},
header={"custom": "value"}
)
print(encrypted)
>>> eyJhbGciOiJSU0ExLjI1NiIsImVuYyI6IkExMjhHQ1Mtc2hhMjU2In0...
Decrypt token¶
Method: jam.jwt_decrypt
Decrypts encrypted JWT.
Args:
token:str- Encrypted JWT token.
Returns:
dict[str, Any]: Decrypted payload.
Raises:
JamJWEDecryptionError- Decryption failed.
data = jam.jwt_decrypt(token=encrypted)
print(data)
>>> {'user_id': 123, 'role': 'admin'}
Use out of instance¶
Built¶
Module: jam.jose.JWT
Args:
alg:str | None- Signing algorithm (JWS).enc:str | None- Content encryption algorithm (JWE). Creates encrypted JWT if specified.secret_key:str | bytes | KeyLike | JWK | None- Key for signing/encryption.password:str | bytes | None- Password for encrypted private keys.list:dict[str, Any] | None- Token list config.serializer:BaseEncoder | type[BaseEncoder] = JsonEncoder- JSON encoder/decoder.logger:BaseLogger- Logger instance.jws:JWS | None- Pre-built JWS instance. If provided, alg is ignored.jwe:JWE | None- Pre-built JWE instance. If provided, enc and secret_key are ignored.
import os
from jam.jose import JWT
jwt = JWT(
alg="RS256",
secret_key=os.getenv("RSA_PRIVATE_KEY")
)
Pre-built JWS/JWE instances¶
For full control over the underlying JWS and JWE instances, you can pass pre-built instances to the JWT constructor:
from jam.jose import JWS, JWE, JWT
# Custom JWS with specific settings
jws = JWS(alg="PS256", key=private_key)
# Custom JWE with specific settings
jwe = JWE(alg="ECDH-ES+A256KW", enc="A256GCM", key=ec_public_key)
# JWT with pre-built instances
jwt = JWT(jws=jws, jwe=jwe)
When using pre-built instances:
- If
jwsis provided,algmust beNone(otherwise raisesJamConfigurationError) - If
jweis provided,encmust beNone(otherwise raisesJamConfigurationError) - At least one of
alg,enc,jws, orjwemust be provided
Factory functions¶
from jam.jose import create_jwt_instance, create_instance
# create_instance is an alias for create_jwt_instance
jwt = create_jwt_instance(
alg="RS256",
secret=private_key, # or secret_key=private_key
password=None,
)
Encode token¶
Method: jwt.encode
Args:
iss:str | None = None- Issuer.sub:str | None = None- Subject.aud:str | None = None- Audience.exp:int | None = None- Lifetime in seconds.nbf:int | None = None- Not-before in seconds from now.jti:str | None = None- JWT ID.header:dict[str, Any] | None = None- Additional header.payload:dict[str, Any] | None = None- Custom data.
Returns:
str: Encoded JWT.
token = jwt.encode(
exp=3600,
sub="user@email.com",
payload={"user_id": 123}
)
Decode token¶
Method: jwt.decode
Args:
token:str- JWT token.validate_claims:bool = True- Validate exp/nbf.
Returns:
dict[str, dict[str, Any]]: Dict with header and payload keys.
Raises:
JamJWTExpired- Token expired.JamJWTNotYetValid- Token not yet valid.JamJWSVerificationError- Invalid signature or token type.
data = jwt.decode(token, validate_claims=True)
print(data["payload"]["sub"])
>>> "user@email.com"
Encrypt token¶
Method: jwt.encrypt
Args:
payload:dict[str, Any] | str- Data to encrypt.header:dict[str, Any] | None = None- Additional header.
Returns:
str: Encrypted JWT.
encrypted = jwt.encrypt(
payload={"data": "sensitive"},
header={"purpose": "auth"}
)
Decrypt token¶
Method: jwt.decrypt
Args:
token:str- Encrypted JWT.
Returns:
dict[str, Any]: Decrypted data.
data = jwt.decrypt(token=encrypted)
print(data)
>>> {"data": "sensitive"}
Generate JTI¶
Property: jwt.jti
Returns:
str: Unique UUID for JWT ID.
jti = jwt.jti
print(jti)
>>> "0c2c38d2-5dcb-4294-bb2d-0820f6ff787d"