Authentication Reference¶
This page documents all authentication and authorization classes.
Authentication backends¶
JWTAuthentication¶
JWT token authentication.
from django_bolt.auth import JWTAuthentication
JWTAuthentication(
secret=None, # JWT secret (default: Django SECRET_KEY)
algorithms=["HS256"], # Allowed algorithms
header="authorization", # Header name
cookie=None, # Cookie name to read the token from
audience=None, # Required audience claim
issuer=None, # Required issuer claim
revocation_store=None, # Token revocation store
oidc_issuer=None, # OIDC discovery issuer
jwks_refresh_interval=300,
)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
secret |
str |
Django SECRET_KEY | HMAC secret (also accepts a PEM public key for compatibility) |
public_key |
str |
None |
PEM public key for asymmetric algorithms (preferred over secret) |
algorithms |
list[str] |
["HS256"] |
Allowed JWT algorithms (token alg must be in this list) |
header |
str |
"authorization" |
Header containing token |
cookie |
str |
None |
Cookie containing token (replaces header when set) |
audience |
str |
None |
Required aud claim |
issuer |
str |
None |
Required iss claim |
leeway |
int |
60 |
Clock-skew tolerance (seconds) for exp/nbf |
token_type |
str |
None |
Required typ claim (e.g. "refresh" for a rotation endpoint); access routes reject typ:"refresh" |
csrf |
bool |
True |
For cookie tokens, enforce a cross-site origin check on unsafe methods that carry the auth cookie |
jwks_url |
str |
None |
HTTPS JWKS endpoint; requires issuer and audience, refreshed at runtime |
jwks |
dict \| str |
None |
JWKS document supplied directly (alternative to jwks_url) |
oidc_issuer |
str |
None |
HTTPS issuer for OIDC discovery; requires audience |
jwks_refresh_interval |
int |
300 |
Periodic remote JWKS refresh interval in seconds |
revocation_store |
RevocationStore | None |
Token revocation store |
Supported algorithms¶
- HMAC:
HS256,HS384,HS512—secretis the shared secret - RSA:
RS256,RS384,RS512,PS256,PS384,PS512—secretis a PEM-encoded RSA public key - ECDSA:
ES256,ES384—secretis a PEM-encoded EC public key - EdDSA:
EdDSA(Ed25519) —secretis a PEM-encoded Ed25519 public key
All algorithms configured on a single backend must use the same kind of
key; you cannot mix HS256 with RS256. Configuration errors — an
unknown algorithm name, algorithms from different key families, or a key
that is not valid PEM — stop the server at startup with a descriptive
error, rather than silently rejecting every token at runtime.
To verify tokens issued by an external identity provider such as Clerk or Auth0, pass the provider's PEM public key:
JWTAuthentication(
public_key=CLERK_PEM_PUBLIC_KEY, # from the provider's dashboard
algorithms=["RS256"],
issuer="https://your-app.clerk.accounts.dev",
)
See Verifying tokens from an identity provider for JWKS configuration.
Validation behavior¶
Tokens are validated in Rust, using verification keys built once at server startup:
- The token's
algheader must appear inalgorithms. A token naming any other algorithm is rejected before signature verification, which prevents algorithm confusion attacks. Header extension parameters of any JSON type are accepted, as RFC 7515 permits; providers such as Clerk and Auth0 include non-string parameters. - The
expclaim is required.expandnbfare checked withleewayseconds of clock-skew tolerance (60 by default). - The
audclaim may be a single string or an array. It is validated only whenaudienceis configured; a token is not rejected merely for carrying anaudclaim when no audience was configured. - When
issueris configured, tokens without anissclaim are rejected. - When the token is read from a cookie, state-changing requests that carry the cookie must also pass a cross-site origin check. See Cross-site request forgery protection.
APIKeyAuthentication¶
In Development
API key permissions (key_permissions parameter) are in development. Basic API key validation works, but per-key permissions are not yet finalized.
API key authentication.
from django_bolt.auth import APIKeyAuthentication
APIKeyAuthentication(
api_keys={"key1", "key2"},
header="x-api-key",
key_permissions={
"key1": {"read", "write"},
"key2": {"read"},
},
)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
api_keys |
set[str] |
set() |
Valid API keys |
header |
str |
"x-api-key" |
Header containing key |
key_permissions |
dict |
None |
Key to permissions mapping |
Permission guards¶
AllowAny¶
Allow any request.
IsAuthenticated¶
Require valid authentication.
Returns 401 if not authenticated.
Requires¶
The permission check: require a token claim to be present, optionally matching expected values.
from django_bolt.auth import Requires
Requires("tenant_id") # claim must exist
Requires("role", "client") # equals (or list contains)
Requires("role", "client", "vip") # any of (OR)
Requires("is_staff", True) # boolean claim
Requires("permissions", "blog.view_article") # Django-style permission
Requires("permissions", all_of=["blog.delete_article", "blog.change_article"]) # AND
Requires("role", none_of=["banned", "suspended"]) # NOR
Requires("role", "client", message="Client accounts only") # custom 403 detail
@api.get("/articles", guards=[Requires("permissions", "blog.view_article")])
Name reusable checks by assignment:
Returns 401 if unauthenticated, 403 if the claim is missing or doesn't match — including for none_of, so an anonymous request can never satisfy an exclusion. message= replaces the 403 detail (never the 401's). The permissions claim reads the unified permission set, so it also covers key_permissions from APIKeyAuthentication; every other claim comes from the JWT payload. See Permissions for full matching semantics.
Token utilities¶
create_jwt_for_user¶
Create a JWT token for a Django user.
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
user |
User | required | Django user instance |
expires_in |
int |
3600 |
Token lifetime in seconds |
extra_claims |
dict |
None |
Additional claims to include |
Token claims¶
The generated token automatically includes:
| Claim | Description |
|---|---|
sub |
User's primary key (as string) |
is_staff |
Staff status |
is_superuser |
Superuser status |
username |
Username |
email |
Email (if available) |
exp |
Expiration timestamp |
iat |
Issued at timestamp |
Note: Permissions are NOT automatically included. Pass them via extra_claims:
get_current_user¶
Dependency for getting the authenticated user.
from django_bolt import Depends
from django_bolt.auth import get_current_user
@api.get("/me")
async def me(user=Depends(get_current_user)):
return {"username": user.username}
create_token_pair¶
Mint an access + refresh token pair. See Access and refresh tokens for usage.
from django_bolt.auth import create_token_pair
pair = create_token_pair(user, method="pwd", kid="signing-key-2026-07")
pair.access_token # JWT with typ "access"
pair.refresh_token # JWT with typ "refresh"
pair.access_claims # decoded claims of the access token
pair.refresh_claims # decoded claims of the refresh token
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
user |
User | str | int |
required | Django user instance, or a bare user id |
secret |
str |
Django SECRET_KEY | Signing key |
algorithm |
str |
"HS256" |
JWT signing algorithm |
kid |
str |
None |
Optional signing-key identifier added to both JWT headers |
access_ttl |
int |
900 |
Access token lifetime in seconds |
refresh_ttl |
int |
604800 |
Refresh token lifetime in seconds |
claims |
dict |
None |
Extra claims copied into both tokens |
method |
str |
None |
Authentication method, recorded as amr (RFC 8176) |
version |
int |
0 |
The user's current token version, embedded as ver |
oat |
int |
now | Origin auth time; set by rotation to carry the original value |
Both tokens carry sub, iat, oat, ver, typ, and exp; the
refresh token additionally carries jti and fam. These lifecycle
claims are reserved — passing one in claims raises ValueError.
rotate_refresh_token¶
Exchange a validated refresh token for a new pair. This function is a
coroutine; the claims you pass are the already-verified claims from
request["context"]["auth_claims"].
from django_bolt.auth import rotate_refresh_token
pair = await rotate_refresh_token(claims, store=store)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
refresh_claims |
dict |
required | Verified claims of the presented refresh token |
store |
RevocationStore | required | Store used for revocation and version checks |
secret |
str |
Django SECRET_KEY | Signing key for the new tokens |
algorithm |
str |
"HS256" |
JWT signing algorithm |
kid |
str |
None |
Optional signing-key identifier added to newly issued JWT headers |
access_ttl |
int |
900 |
New access token lifetime in seconds |
refresh_ttl |
int |
604800 |
New refresh token lifetime in seconds |
rotate |
bool |
True |
Issue a new refresh token and revoke the old one; False issues an access token only |
max_session_lifetime |
int |
None |
Maximum seconds since the original authentication (oat) |
leeway |
int |
60 |
Clock-skew tolerance; must match the validating JWT backend |
claims |
dict |
None |
Extra claims for the new tokens |
Raises TokenRotationError when the token has no jti, has been
revoked or reused, belongs to a revoked family, carries a stale ver,
or exceeds max_session_lifetime. Return a generic 401 to the client
in that case.
set_token_cookies¶
Attach a token pair to a response as HttpOnly, Secure,
SameSite=Lax cookies. Returns the response for chaining.
from django_bolt.auth import set_token_cookies
return set_token_cookies(response, pair, refresh_path="/auth/refresh")
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
response |
object | required | Any response exposing set_cookie() |
pair |
TokenPair | required | The pair to attach |
access_cookie |
str |
"access_token" |
Access cookie name |
refresh_cookie |
str |
"refresh_token" |
Refresh cookie name |
refresh_path |
str |
"/" |
Path the refresh cookie is scoped to; set this to your rotation endpoint |
secure |
bool |
True |
Send cookies over HTTPS only |
samesite |
str |
"Lax" |
SameSite attribute |
domain |
str |
None |
Cookie domain |
Each cookie's max_age is derived from its token's own exp claim.
Revocation stores¶
When passed as JWTAuthentication(revocation_store=store), the framework
checks every authenticated request against the store and rejects revoked
tokens with 401. You don't call is_revoked() from your handlers.
All revoke() calls accept an exp keyword: pass the token's own exp
claim so the entry expires exactly when the token would have. Call
sites without exp fall back to default_ttl (set per instance) or to
the module-level _DEFAULT_TTL_SECONDS (7 days).
Store methods¶
All methods are coroutines.
| Method | Description |
|---|---|
revoke(jti, *, exp=None) |
Revoke a single token by its jti claim |
is_revoked(jti) |
Whether a token has been revoked |
consume(jti, *, exp=None) |
Atomically consume a refresh token; True only for the first caller |
get_user_version(user_id) |
The user's current token version (0 if never bumped) |
bump_user_version(user_id) |
Increment the version, invalidating earlier refresh tokens at rotation |
revoke_family(fam, *, exp=None) |
Revoke an entire refresh-token rotation family |
is_family_revoked(fam) |
Whether a rotation family has been revoked |
The version and family methods support the access and refresh token
lifecycle. They
are implemented by InMemoryRevocation and DjangoCacheRevocation.
DjangoORMRevocation supports atomic consumption and family revocation,
but not user-version methods.
InMemoryRevocation¶
In-memory token revocation (development only — single process, no persistence).
from django_bolt.auth import InMemoryRevocation
store = InMemoryRevocation()
await store.revoke("token-jti", exp=1234567890)
await store.is_revoked("token-jti") # True
DjangoCacheRevocation¶
Django cache-based revocation.
from django_bolt.auth import DjangoCacheRevocation
store = DjangoCacheRevocation(
cache_alias="default",
key_prefix="revoked:",
default_ttl=86400 * 7, # fallback when revoke() is called without exp
)
Use Redis or Memcached when refresh rotation or user-version bumps must
work across processes. Those operations require atomic cache add and
incr; file-based, database, dummy, and other non-atomic caches are
rejected. Ensure the cache does
not evict security entries before their configured TTL.
DjangoORMRevocation¶
Database-backed revocation.
from django_bolt.auth import DjangoORMRevocation
store = DjangoORMRevocation(
model="myapp.RevokedToken", # 'app_label.ModelName' — exactly two parts
default_ttl=86400 * 7,
)
Requires a model with jti (unique, indexed) and expires_at
(indexed) fields, plus a periodic cleanup task that deletes rows where
expires_at < now().
Authentication context¶
After authentication, request.context contains:
| Key | Type | Description |
|---|---|---|
user_id |
str |
User identifier |
is_staff |
bool |
Staff status |
is_superuser |
bool |
Superuser status |
auth_backend |
str |
Backend name (jwt, api_key) |
permissions |
list[str] |
User permissions |
auth_claims |
dict |
JWT claims (JWT only) |