Backends¶
Use this page for the database-token preset, backend lifecycle helpers, route path flags, and built-in auth payloads.
Opaque DB-token preset¶
Use DatabaseTokenAuthConfig plus direct LitestarAuthConfig(..., database_token_auth=...)
construction for the common bearer + database-token flow. This is the documented entrypoint for
opaque DB tokens; it builds the AuthenticationBackend, BearerTransport, and
DatabaseTokenStrategy for you.
from uuid import UUID
from litestar import Litestar
from litestar_auth import DatabaseTokenAuthConfig, LitestarAuth, LitestarAuthConfig
from litestar_auth.manager import UserManagerSecurity
from litestar_auth.models import User
config = LitestarAuthConfig[User, UUID](
database_token_auth=DatabaseTokenAuthConfig(
token_hash_secret="replace-with-32+-char-db-token-secret",
),
user_model=User,
user_manager_class=UserManager,
session_maker=session_maker,
user_manager_security=UserManagerSecurity(
verification_token_secret="replace-with-32+-char-secret",
reset_password_token_secret="replace-with-32+-char-secret",
),
)
app = Litestar(plugins=[LitestarAuth(config)])
Here, session_maker means a callable session factory the plugin can invoke as session_maker() to obtain the request-local AsyncSession. async_sessionmaker(...) is the most common implementation, but any factory with that runtime contract is supported.
If you previously hand-assembled AuthenticationBackend(..., transport=BearerTransport(), strategy=DatabaseTokenStrategy(...)), migrate that setup to the direct database_token_auth=DatabaseTokenAuthConfig(...) form above. Keep manual backends= assembly only when you need multiple backends, custom token models, or another custom transport/strategy mix.
When you use database_token_auth=..., config.backends stays empty by design. config.resolve_startup_backends() returns startup-only StartupBackendTemplate values for plugin assembly and validation, while config.resolve_backends(session) returns the request-scoped runtime AuthenticationBackend instances bound to the active AsyncSession for every supported backend configuration.
Default precedence¶
Feature defaults are resolved once from the config snapshot used for plugin startup:
- Explicit values on
DatabaseTokenAuthConfig,ApiKeyConfig,TotpConfig, andOAuthConfigwin. - Omitted feature objects keep that feature disabled, except
LitestarAuthConfig.api_keys, whose default object is disabled withenabled=False. - Omitted
user_db_factoryfalls back to the lazy SQLAlchemy store factory at request binding time. - Omitted
TotpConfig.totp_backend_nameselects the primary startup backend.
None is meaningful only where documented, such as ApiKeyConfig.default_ttl=None for non-expiring keys. Other omitted fallback targets are normalized internally before startup wiring so defaults stay coherent across route, backend, and manager assembly.
Backend lifecycle contract¶
Treat the two backend helpers as distinct surfaces:
config.resolve_startup_backends()is the plugin's startup inventory. Manual backends are wrapped asStartupBackendTemplatevalues, and the DB-token preset also contributes aStartupBackendTemplatethat is valid for plugin assembly, validation, and route selection only.config.resolve_backends(session)realizes request-scopedAuthenticationBackendinstances aligned withconfig.resolve_startup_backends()order. Use this surface whenever runtime login, refresh, logout, token validation, or other request-time work needs the activeAsyncSession.- Startup-only DB-token templates fail closed if they are used for runtime database-token work before
resolve_backends(session)supplies a request session.
Controller selection follows that startup inventory:
- The primary backend mounts at
{auth_path}. - Additional backends mount at
{auth_path}/{backend.name}in configured order. - Plugin-owned OAuth login routes use the primary startup backend.
- TOTP uses the primary startup backend unless
TotpConfig.totp_backend_nameselects another named backend from the same startup inventory.
API-key backend¶
ApiKeyConfig is opt-in through LitestarAuthConfig.api_keys. Set api_keys.enabled=True to add
the bearer-mode API-key backend, management controllers, rate-limit slots, and the OpenAPI
apiKeyAuth security scheme. When disabled, the plugin does not add an API-key backend,
dependency, OpenAPI security scheme, or controller surface.
from uuid import UUID
from litestar_auth import ApiKeyConfig, LitestarAuthConfig
from litestar_auth.manager import UserManagerSecurity
from litestar_auth.models import User
config = LitestarAuthConfig[User, UUID](
api_keys=ApiKeyConfig(
enabled=True,
environment_marker="prod",
allowed_scopes=("read", "write"),
),
user_model=User,
user_manager_class=UserManager,
session_maker=session_maker,
user_manager_security=UserManagerSecurity(
verification_token_secret="replace-with-32+-char-secret",
reset_password_token_secret="replace-with-32+-char-secret",
api_key_hash_secret="replace-with-32+-char-api-key-hmac-secret",
),
)
The plugin appends the generated API-key backend after any manually configured backends or the
database-token preset, so existing primary-backend route selection stays caller-controlled. Request
authentication accepts API keys through either Authorization: Bearer ak_<env>_<key_id>.<secret>
or X-API-Key: ak_<env>_<key_id>.<secret>.
API-key backends are standalone authenticators and do not participate in refresh-token flows. When
enable_refresh=True, startup refresh-capability validation still applies to refresh-relevant
Bearer/Cookie/database/Redis token backends, but intentionally skips ApiKeyTransport backends.
Set signing_required=true on ApiKeyCreateRequest or pass
create_api_key(..., signing_required=True) to issue a key that can only authenticate signed
requests. Signing mode uses Authorization: LSA1-HMAC-SHA256 Credential=<key_id>,
SignedHeaders=<semicolon-separated-lowercase-header-names>, Signature=<hex-hmac> plus
X-Auth-Date and X-Auth-Nonce. The signed request string is method, path, sorted query string,
the declared signed headers, the signed header list, and the SHA-256 body digest. Bearer attempts for
signing-required keys fail with API_KEY_SIGNATURE_INVALID.
To enable signing, configure api_keys.signing_enabled=True, api_keys.nonce_store, and
api_keys.secret_encryption_keyring. Nonce TTL is 2 * signing_skew_seconds; timestamp failures
return API_KEY_SIGNATURE_TIMESTAMP_SKEW, nonce replays return API_KEY_SIGNATURE_NONCE_REPLAY,
and other signature failures return API_KEY_SIGNATURE_INVALID. The encryption keyring must be
distinct from the API-key hash secret and all other configured secret roles. In multi-worker
deployments, use RedisApiKeyNonceStore; the in-memory nonce store is process-local and intended
for tests or single-process development. Signed requests are buffered before authentication so the
server can verify the raw body digest; api_keys.signed_body_max_bytes caps that pre-auth buffer
and defaults to 1048576 bytes. Requests over the cap fail with HTTP 413 and
REQUEST_BODY_INVALID.
With API keys enabled, the plugin mounts self-service routes at /api-keys:
POST /api-keyscreates a key and returnsApiKeyCreateResponsewith the rawapi_keyexactly once plus safekeymetadata. The request body isApiKeyCreateRequestand requirescurrent_passwordby default whenapi_keys.require_step_up_on_create=True.GET /api-keysreturnsApiKeyListResponse.GET /api-keys/{key_id}returns safeApiKeyReadmetadata for a key owned by the current user.PATCH /api-keys/{key_id}acceptsApiKeyUpdateRequestfor name/scope changes and requirescurrent_password.DELETE /api-keys/{key_id}soft-revokes a current-user key.
Admin routes are nested under the configured users_path and guarded by is_superuser:
POST /users/{user_id}/api-keys, GET /users/{user_id}/api-keys, and
DELETE /users/{user_id}/api-keys/{key_id}. The user_id always comes from the path; request
bodies cannot select another user, and admin create requests do not require the target user's
current_password. Self-service lookups for another user's key_id return API_KEY_INVALID as a
404 so key existence is not disclosed.
Startup validation is fail-closed when api_keys.enabled=True: api_key_hash_secret is required on
UserManagerSecurity, max_keys_per_user must be greater than zero, and allowed_scopes must be
non-empty while scope_subset_check=True. The default SQLAlchemy API-key store is imported lazily at
request binding time; pass store_factory= to use a custom BaseApiKeyStore.
default_ttl defaults to 365 days. Setting default_ttl=None is accepted for explicit non-expiring
key policies, but production startup emits SecurityWarning unless unsafe_testing=True.
Request-scoped BaseUserManager instances receive the API-key store and config when
api_keys.enabled=True. Use the manager surface to issue and maintain keys:
create_api_key(user, name=..., scopes=..., current_password=...)returns anApiKeyCreateResultwhosesecret.get_secret_value()is the only place the rawak_<env>_<key_id>.<secret>value is exposed. The persisted row stores only the HMAC digest for bearer keys. Signing-required keys also store the raw signing secret encrypted asfernet:v1:<keyring-key-id>:<ciphertext>.list_api_keys()andget_api_key()return metadata rows only; passinclude_inactive=Truewhen an operator view needs revoked or expired keys.update_api_key()can rename a key and replace its scope list. Create and update reject scopes outsideallowed_scopeswhilescope_subset_check=True.revoke_api_key()is soft and idempotent; repeated calls preserve the firstrevoked_at.record_api_key_used()honorslast_used_write_strategyandlast_used_throttle_seconds, so regular API-key authentication does not write the same row on every request.api_key_signing_secret_requires_reencrypt(row)andreencrypt_api_key_signing_secret(row_or_key_id)are explicit row-level helpers for rotating signing-required keys afterapi_keys.secret_encryption_keyring.active_key_idchanges. They require an API-key store and keyring, reject bearer rows and missingencrypted_secretvalues, never return plaintext signing secrets, and do not run create/revoke/use lifecycle hooks.
create_api_key() and update_api_key() verify current_password when it is supplied. The
generated HTTP create route requires that field by default through
api_keys.require_step_up_on_create=True; setting it to False keeps the password-session guard but
skips password re-verification for create. The update route always requires current_password.
API-key-authenticated callers cannot create or mutate keys because the generated routes also use
requires_password_session.
Applications can override on_after_api_key_created, on_after_api_key_revoked, and
on_after_api_key_used for audit events; the used hook fires only when a last-used write is
actually persisted.
API-key management failures use stable machine-readable codes: API_KEY_SCOPE_DENIED for scopes
outside allowed_scopes, API_KEY_LIMIT_REACHED for max_keys_per_user, and API_KEY_INVALID
for missing or foreign key ids. API-key authentication failures distinguish API_KEY_INVALID,
API_KEY_REVOKED, and API_KEY_EXPIRED where the credential can be parsed and the key row can be
resolved.
Rate limiting exposes two API-key slots in AuthRateLimitConfig: api_key_create for
POST /api-keys, and api_key_use for failed API-key authentication attempts keyed by the parsed
key_id when available. Successful API-key requests update last_used_at according to the manager
throttle policy, but do not consume the invalid-attempt bucket. The api_key_use identity uses the same rightmost X-Forwarded-For
trusted-proxy handling as the other auth rate-limit helpers for its IP component.
Paths and HTTP feature flags¶
| Field | Default | Meaning |
|---|---|---|
auth_path |
"/auth" |
Base path for generated auth controllers. |
users_path |
"/users" |
Base path for user CRUD when enabled. |
include_register |
True |
POST .../register |
include_verify |
True |
Verify + request-verify-token |
include_reset_password |
True |
Forgot + reset password |
include_users |
False |
User management routes |
enable_refresh |
False |
POST .../refresh |
include_session_devices |
False |
GET .../sessions, POST .../sessions, DELETE .../sessions/{session_id}, POST .../sessions/revoke-others |
requires_verification |
True |
Stricter login / TOTP-verify policy |
hard_delete |
False |
Physical vs soft delete semantics for user delete |
login_identifier |
"email" |
"email" or "username" for POST {auth_path}/login credential lookup |
Multiple backends: first backend → {auth_path}; others → {auth_path}/{backend-name}/....
When requires_verification=True, the shared account-state policy is consistent across login,
refresh, and TOTP verification: inactive users fail first, and unverified users fail next.
When refresh is enabled with CookieTransport, include_session_devices=True routes read the
dedicated refresh cookie to identify the current refresh session for is_current markers and
revoke-others preservation. Bearer clients do not have a refresh cookie; they may pass the existing
RefreshTokenRequest body to POST {auth_path}/sessions and
POST {auth_path}/sessions/revoke-others when they need current-session detection. If no current
refresh credential can be resolved, revoke-others fails closed; with the built-in DB token strategy,
that means all active refresh sessions for the current user are revoked.
Session/device management setup¶
Use include_session_devices=True only with a backend strategy that implements refresh-session
management. The built-in DB token strategy does; JWT and Redis token strategies do not provide a
session/device dashboard in this slice and return SESSION_MANAGEMENT_UNSUPPORTED if these routes
are mounted against them.
The plugin-managed bearer DB-token preset is the shortest setup:
config = LitestarAuthConfig[User, UUID](
database_token_auth=DatabaseTokenAuthConfig(
token_hash_secret="replace-with-32+-char-db-token-secret",
),
user_model=User,
user_manager_class=UserManager,
session_maker=session_maker,
user_manager_security=user_manager_security,
enable_refresh=True,
include_session_devices=True,
)
Bearer clients receive refresh tokens in response bodies. To mark the current session in a list response, call:
POST /auth/sessions
Authorization: Bearer <access-token>
Content-Type: application/json
{"refresh_token": "<current-refresh-token>"}
For browser refresh sessions, assemble a DB-token backend with CookieTransport and the same
request-scoped session-binding contract. The transport keeps the access token in cookie_name and
the refresh token in the dedicated <cookie_name>_refresh HttpOnly cookie:
from datetime import timedelta
from uuid import UUID
from litestar_auth import AuthenticationBackend, CookieTransport, LitestarAuthConfig
from litestar_auth.authentication.strategy import DatabaseTokenStrategy
from litestar_auth.models import User
cookie_db_backend = AuthenticationBackend[User, UUID](
name="database-cookie",
transport=CookieTransport(
cookie_name="app_auth",
max_age=15 * 60,
refresh_max_age=30 * 24 * 60 * 60,
),
strategy=DatabaseTokenStrategy[User, UUID](
session=session_maker(),
token_hash_secret="replace-with-32+-char-db-token-secret",
max_age=timedelta(minutes=15),
refresh_max_age=timedelta(days=30),
),
)
config = LitestarAuthConfig[User, UUID](
backends=(cookie_db_backend,),
user_model=User,
user_manager_class=UserManager,
session_maker=session_maker,
user_manager_security=user_manager_security,
csrf_secret="replace-with-32+-char-csrf-secret",
enable_refresh=True,
include_session_devices=True,
)
AuthenticationBackend.with_session(...) rebinds DatabaseTokenStrategy for each request when the
plugin resolves runtime backends. Keep session_maker configured so the generated controllers,
refresh flow, and session/device routes all use the same request-local SQLAlchemy session.
Built-in auth payload boundary¶
The generated controllers do not use one universal credential field. login_identifier only changes how LoginCredentials.identifier is interpreted on POST {auth_path}/login.
| Route | Built-in request schema | Published fields |
|---|---|---|
POST {auth_path}/login |
LoginCredentials |
identifier, password |
POST {auth_path}/register |
UserCreate |
email, password |
POST {auth_path}/request-verify-token |
RequestVerifyToken |
email |
POST {auth_path}/verify |
VerifyToken |
token |
POST {auth_path}/forgot-password |
ForgotPassword |
email |
POST {auth_path}/reset-password |
ResetPassword |
token, password |
Verify and reset token values are signed JWTs issued by the manager security service. Library-issued
tokens include JOSE typ=JWT, and decode rejects tokens with a missing or unexpected typ header
before the normal signed audience, required-claim, and password-fingerprint validation. Custom test
fixtures or advanced integrations that mint these tokens directly must set the same header; the
header check is not a replacement for the existing signed JWT validation.
By default, built-in TOTP routes publish TotpEnableRequest, TotpConfirmEnableRequest, TotpVerifyRequest, and TotpDisableRequest. TotpVerifyRequest.code and TotpDisableRequest.code accept either a current TOTP code or an unused recovery code where documented. The built-in TOTP flow still uses user.email for the otpauth URI and password step-up, even when login_identifier="username".