JWT Authentication and AsyncJwtBearer¶
This page documents the JWT helpers and the AsyncJwtBearer class in ninja_aio/auth.py, including configuration, validation, and usage in Django Ninja.
Overview¶
AsyncJwtBearer: Asynchronous HTTP Bearer auth that verifies JWTs, validates claims via a registry, and delegates user resolution toauth_handler.- Helpers:
validate_key: Ensures JWK keys are present and of the correct type.validate_mandatory_claims: Ensuresissandaudare present (from settings if not provided).encode_jwt: Signs a JWT with time-based claims (iat,nbf,exp) and mandatoryiss/aud.decode_jwt: Verifies and decodes a JWT with a public key and allowed algorithms.
Configuration without settings¶
Settings are not required. Provide keys and claims explicitly:
- Pass
private_keytoencode_jwtandpublic_keytodecode_jwt/AsyncJwtBearer.jwt_public. - Include
issandauddirectly in theclaimsyou encode if you are not using settings.
Example key usage without settings:
# ...existing code...
from joserfc import jwk
from ninja_aio.auth import encode_jwt, decode_jwt
private_key = jwk.RSAKey.import_key(open("priv.jwk").read())
public_key = jwk.RSAKey.import_key(open("pub.jwk").read())
token = encode_jwt(
claims={"sub": "123", "iss": "https://auth.example", "aud": "my-api"},
duration=3600,
private_key=private_key,
algorithm="RS256",
)
decoded = decode_jwt(token=token, public_key=public_key, algorithms=["RS256"])
# ...existing code...
Mandatory claims¶
The library enforces iss and aud via JWT_MANDATORY_CLAIMS. If you do not use settings, include them in the payload you pass to encode_jwt.
Configuration with settings (optional)¶
You can centralize configuration in Django settings and omit explicit keys/claims:
JWT_PRIVATE_KEY: jwk.RSAKey or jwk.ECKey for signingJWT_PUBLIC_KEY: jwk.RSAKey or jwk.ECKey for verificationJWT_ISSUER: issuer stringJWT_AUDIENCE: audience string
When present:
encode_jwtreadsJWT_PRIVATE_KEYifprivate_keyis not passed, and fillsiss/audviavalidate_mandatory_claimsif missing.decode_jwtreadsJWT_PUBLIC_KEYifpublic_keyis not passed.AsyncJwtBearercan read the public key from settings by assigningjwt_public = settings.JWT_PUBLIC_KEY.
# settings.py (example)
JWT_PRIVATE_KEY = jwk.RSAKey.import_key(open("priv.jwk").read())
JWT_PUBLIC_KEY = jwk.RSAKey.import_key(open("pub.jwk").read())
JWT_ISSUER = "https://auth.example"
JWT_AUDIENCE = "my-api"
Usage without passing keys/claims explicitly:
from ninja_aio.auth import encode_jwt, decode_jwt
# claims missing iss/aud will be completed from settings
token = encode_jwt(claims={"sub": "123"}, duration=3600)
decoded = decode_jwt(token=token) # uses settings.JWT_PUBLIC_KEY
AsyncJwtBearer wired to settings:
from django.conf import settings
from ninja_aio.auth import AsyncJwtBearer
class SettingsBearer(AsyncJwtBearer):
jwt_public = settings.JWT_PUBLIC_KEY
claims = {
"iss": {"value": settings.JWT_ISSUER},
"aud": {"value": settings.JWT_AUDIENCE},
# Optionally require time-based claims:
# "exp": {"essential": True},
# "nbf": {"essential": True},
}
async def auth_handler(self, request):
sub = self.dcd.claims.get("sub")
return {"user_id": sub}
AsyncJwtBearer¶
Key points¶
jwt_public: Must be a JWK (RSA or EC) used to verify signatures.claims: Dict passed tojwt.JWTClaimsRegistrydefining validations (e.g.,iss,aud,exp,nbf).algorithms: Allowed algorithms (default["RS256"]).dcd: Set after successful decode; instance ofjwt.Tokencontainingheaderandclaims.get_claims(): Builds the claim registry fromclaims.validate_claims(claims): Validates decoded claims; raisesjose.errors.JoseErroron failure.auth_handler(request): Async hook to resolve application user given the decoded token (self.dcd).authenticate(request, token): Decodes, validates, and delegates toauth_handler. Returns user orFalse.
Example¶
from joserfc import jwk
from ninja import NinjaAPI
from ninja_aio.auth import AsyncJwtBearer
class MyBearer(AsyncJwtBearer):
jwt_public = jwk.RSAKey.import_key(open("pub.jwk").read())
claims = {
"iss": {"value": "https://auth.example"},
"aud": {"value": "my-api"},
# You can add time-based checks if needed:
# "exp": {"essential": True},
# "nbf": {"essential": True},
}
async def auth_handler(self, request):
sub = self.dcd.claims.get("sub")
return {"user_id": sub}
api = NinjaAPI()
@api.get("/secure", auth=MyBearer())
def secure_endpoint(request):
return {"ok": True}
Claims registry helper¶
You can construct and reuse a registry from your class-level claims:
registry = MyBearer.get_claims()
# registry.validate(token_claims) # raises JoseError on failure
encode_jwt¶
Signs a JWT with safe defaults:
- Adds
iat,nbf, andexpusing timezone-awaretimezone.now(). - Ensures
issandaudare present viavalidate_mandatory_claims(include them inclaimsif not using settings). - Header includes
alg,typ=JWT, and optionalkid.
from joserfc import jwk
from ninja_aio.auth import encode_jwt
private_key = jwk.RSAKey.import_key(open("priv.jwk").read())
claims = {"sub": "123", "scope": "read", "iss": "https://auth.example", "aud": "my-api"}
token = encode_jwt(
claims=claims,
duration=3600,
private_key=private_key,
algorithm="RS256",
)
decode_jwt¶
Verifies and decodes a JWT with a public key and algorithm allow-list.
from joserfc import jwk
from ninja_aio.auth import decode_jwt
public_key = jwk.RSAKey.import_key(open("pub.jwk").read())
decoded = decode_jwt(
token=token,
public_key=public_key,
algorithms=["RS256"],
)
claims = decoded.claims
sub = claims.get("sub")
validate_key¶
If you do not use settings, pass keys directly. validate_key will raise ValueError only when neither an explicit key nor a configured setting is provided.
from ninja_aio.auth import validate_key
from joserfc import jwk
pkey = validate_key(jwk.RSAKey.import_key(open("priv.jwk").read()), "JWT_PRIVATE_KEY")
validate_mandatory_claims¶
Ensures iss and aud are present; if settings are not used, include them in your input claims.
from ninja_aio.auth import validate_mandatory_claims
claims = {"sub": "123", "iss": "https://auth.example", "aud": "my-api"}
claims = validate_mandatory_claims(claims)
Error handling¶
authenticatereturnsFalseon decode (ValueError) or claim validation failure (JoseError). Map this to 401/403 in your views as needed.validate_claimsraisesjose.errors.JoseErrorfor invalid claims.encode_jwtanddecode_jwtraiseValueErrorfor missing/invalid keys or configuration.
Security notes¶
- Rotate keys and use
kidheaders to support key rotation. - Validate critical claims (
exp,nbf,iss,aud) via the registry. - Do not log raw tokens or sensitive claims.