Litestar Security
litestar-security 0.6.0 is a declarative authentication and authorization framework for Litestar. It provides credential slots and mechanisms, unified session management, local accounts, MFA, WebAuthn passkeys, OAuth/OIDC, API keys, workload JWTs, and browser hardening.
Two separate axes, wired through two separate Litestar keywords:
- Authentication — who is calling — is a policy on
auth=(oropt={"auth": ...}). - Authorization — what they may do — is a predicate in Litestar's native
guards=[...].
Do not conflate them: the policy helpers (public, required, any_of, all_of, at_least, optional, exclude, mechanism) take mechanism names, while the guard combinators (requires_any_of, requires_all_of, requires_at_least, requires_one_of) take predicates.
Code Style Rules
- Declare authentication with
auth=. Put policy on the route, or on a router/controller/app throughopt={"auth": ...}. The nearest native owner wins. - Keep authorization in
guards=[...]. Litestar'ssecurity=parameter is reserved for the OpenAPI requirements projected fromauth. - Inject the user with
CurrentUser[T]. UseNamedDependency[CurrentUser[UserType]]; it rejects anonymous and userless service principals.principalandsecurity_contextstay typed on public routes too. - Authorize from the snapshot. Guards read the
AuthorizationSnapshotproduced by the configuredauthorization_resolver. Never query the database inside a guard. - Compose predicates with
requires_*. Userequires_any_of,requires_all_of,requires_at_least,requires_one_offor predicate composition. - Exclude other plugins' routes by path. Static assets and dashboards carry no
authand compile to implicitrequired(), so they answer401until listed inSecurityConfig(exclude=[...]). Inspect routes withlitestar security routes. - Secure WebSockets with connect tokens. Browsers cannot set handshake headers; mint a short-lived token over authenticated HTTP via
WebSocketConnectTokenServiceorWebSocketConnectTokenIssuer. - Load protector keys from a secret store. MFA and OAuth protectors need application-owned 32-byte AES-256-GCM keys, never source literals.
Quick Reference
Plugin Registration
from litestar import Litestar, get
from litestar.di import NamedDependency
from litestar_security import (
SecurityConfig,
SecurityContext,
SecurityPlugin,
public,
)
@get("/", auth=public(), sync_to_thread=False)
def index(security_context: NamedDependency[SecurityContext]) -> dict[str, bool]:
return {"authenticated": bool(security_context.evidence)}
app = Litestar(
route_handlers=[index],
plugins=[SecurityPlugin(SecurityConfig())],
)
With mechanisms configured and no inherited policy, routes default to implicit required(). With no mechanisms at all they are public.
Authentication Policy
from litestar import Controller, get
from litestar_security import all_of, any_of, at_least, public, required
policy_default = required()
policy_session = required("session")
policy_either = any_of("session", "api-key")
policy_both = all_of("api-key", "service-jwt")
policy_threshold = at_least(2, "session", "api-key", "service-jwt")
policy_public = public()
Apply it at whichever layer owns the decision:
@get("/health", auth=public())
async def health() -> dict[str, str]:
return {"status": "ok"}
class AccountController(Controller):
opt = {"auth": required("session")}
Custom controller class attributes are not propagated by Litestar — policy must live in opt, or use the typed SecureController / PublicController base classes.
Authorization Guards
from litestar import Controller, get
from litestar_security import requires_any_of, requires_role, requires_scope
class ReportsController(Controller):
path = "/reports"
opt = {"auth": required("session")}
guards = [requires_role("analyst")]
@get("/", guards=[requires_any_of(requires_scope("read:all"), requires_scope("read:reports"))])
async def list_reports(self) -> list[dict[str, str]]:
return []
Reserved Dependency Names
The plugin registers these; do not shadow them.
| Key | Type | Use |
|---|---|---|
principal |
Principal |
Stable envelope identity plus the active user model |
security_context |
SecurityContext |
Active session, evidence, snapshot, and restrictions |
current_user |
CurrentUser[User] |
Narrowing shortcut; rejects anonymous and service principals |
websocket_connect_tokens |
WebSocketConnectTokenService |
WebSocket connect-token manager |
Status Code Contract
| Outcome | Status |
|---|---|
| Authentication failure | 401 |
| Guard denial | 403 |
| Verification unavailable (fails closed) | 503 |
Workflow
Step 1: Install the capabilities in use
Core install covers JWT/JWKS validation, API keys, IAP, and OIDC token verification. Use [argon2,mfa] for LocalAuth; add [passkeys] or [oauth] only when needed, or use [all].
Step 2: Choose providers
Pick where identity is established — local accounts, OAuth/OIDC, Google IAP, API keys, or workload JWTs. Adding a provider makes its mechanism available; route policy decides where it is accepted. See Providers.
Step 3: Implement the authorization resolver
Implement an async resolve(principal) method that returns an AuthorizationSnapshot of granted roles, scopes, capabilities, tenant roles, and tenant IDs. Return InvalidCredentials or VerificationUnavailable for expected denial or dependency failure. It runs once per request, so guards must not perform I/O.
from litestar_security import AuthorizationSnapshot, Principal
class AppAuthorizationResolver:
async def resolve(self, principal: Principal[User]) -> AuthorizationSnapshot:
if not principal.is_authenticated:
return AuthorizationSnapshot()
user = principal.require_user()
return AuthorizationSnapshot(
roles=frozenset(user.roles),
scopes=frozenset(user.scopes),
)
Step 4: Register the plugin and set default policy
Scope an application-wide opt={"auth": ...} default to the router that owns the application's own routes, so policy-less third-party routes keep the implicit default rather than counting as declared.
Step 5: Exclude routes the application did not write
Add SecurityConfig(exclude=[...]) patterns for static files, queue dashboards, and schema browsers. See Composition.
Step 6: Harden the deployment
Apply SecurityHeadersConfig.hardened(), supply every CSP directive explicitly, and move protector keys and peppers into secret management. See Hardening.
Guardrails
- Do not pass predicates to
any_of/all_of/at_least. Those compose authentication mechanisms. Userequires_any_of,requires_all_of,requires_at_least, orrequires_one_offor predicates. - Do not shadow reserved dependencies. Avoid naming providers
principal,security_context,current_user, orwebsocket_connect_tokens. - Do not perform I/O in predicates. Guards evaluate synchronously against the snapshot; put database checks in the
authorization_resolver. - Do not use
guards=for authentication orauth=for authorization. They compile to different things — runtime admission plus OpenAPI projection versus permission checks. - Do not put a layer-level policy above excluded routes. A route that both declares
authand matches an exclusion pattern is rejected at startup. - Do not put bearer credentials in WebSocket query strings. Use a connect token or an HttpOnly cookie.
- Do not enable
MFAConfig.require_at_loginbefore enrolling factors. Affected accounts lock themselves out. - Do not hard-code protector keys. Load exact 32-byte material from a KMS or secret store, and retain the previous key through rotation.
Validation Checkpoint
-
SecurityPluginis registered in applicationplugins. - Every route's authentication policy is declared via
auth=or inheritedopt={"auth": ...}. - Authorization uses
guards=[...]with predicates, never the mechanism combinators. - A custom
authorization_resolverimplements asyncresolve()and returns anAuthorizationSnapshot,InvalidCredentials, orVerificationUnavailable. - No handler or guard queries the database to perform authorization checks.
- Handler injection uses
CurrentUser[UserType]orNamedDependency[CurrentUser[UserType]]. - Routes registered by other plugins are excluded by anchored path pattern or given an explicit policy.
- WebSockets use connect tokens verified against the registered handler name and exact Origin.
- Exception handlers cover
401,403, and503outcomes. - Protector keys, peppers, and session secrets come from secret management.
Example
from dataclasses import dataclass, field
from litestar import Litestar, Router, get
from litestar.di import NamedDependency
from litestar_security import (
AuthorizationSnapshot,
CurrentUser,
Principal,
SecurityConfig,
SecurityHeadersConfig,
SecurityPlugin,
public,
required,
requires_any_of,
requires_role,
requires_scope,
)
@dataclass
class User:
id: str
username: str
roles: list[str] = field(default_factory=list)
scopes: list[str] = field(default_factory=list)
class AppAuthorizationResolver:
async def resolve(self, principal: Principal[User]) -> AuthorizationSnapshot:
if not principal.is_authenticated:
return AuthorizationSnapshot()
user = principal.require_user()
return AuthorizationSnapshot(
roles=frozenset(user.roles),
scopes=frozenset(user.scopes),
)
@get("/health", auth=public())
async def health() -> dict[str, str]:
return {"status": "ok"}
@get(
"/orders",
guards=[requires_any_of(requires_scope("read:all"), requires_scope("read:orders"))],
)
async def list_orders(current_user: NamedDependency[CurrentUser[User]]) -> dict[str, str]:
return {"owner": current_user.username}
@get("/admin/orders", guards=[requires_role("admin")])
async def admin_orders() -> list[dict[str, str]]:
return []
api = Router(path="/api", route_handlers=[list_orders, admin_orders], opt={"auth": required("session")})
security_config = SecurityConfig[User](
authorization_resolver=AppAuthorizationResolver(),
headers=SecurityHeadersConfig.hardened(),
exclude=["^/static"],
)
app = Litestar(
route_handlers=[health, api],
plugins=[SecurityPlugin(config=security_config)],
)
References Index
- Authentication — policy helpers, ownership layers, controller base classes, CSRF interaction.
- Authorization — snapshots, resolvers, predicates, combinators, tenant checks, assurance.
- Providers — local accounts, OAuth/OIDC, IAP, API keys, workload JWTs, transaction protectors.
- Composition — excluding routes other plugins register, and the patterns per plugin.
- Hardening — CSP, security headers, secrets, key rotation, MFA operational rules.
- WebSockets — connect tokens, close codes, snapshot refresh, revocation.
Cross-References
- litestar — Litestar app setup and plugin list.
- litestar-auth-guards — native guards and low-level ASGI connection context.
- litestar-exceptions — mapping
401/403/503to Problem Details responses.
Official References
- https://github.com/cofin/litestar-security
- https://github.com/cofin/litestar-security/tree/v0.6.0/docs
- https://github.com/cofin/litestar-security/tree/v0.6.0/examples