Immutable. This exact content is served forever at /api/v1/blob/7a68d5ca96c0324b.
---
name: litestar-security
description: "Auto-activate for litestar_security, SecurityPlugin, SecurityConfig, CurrentUser, Principal, SecurityContext, requires_role, requires_scope, requires_authenticated, requires_tenant, requires_tenant_role, requires_capability, or requires_assurance. Not for raw auth guards alone — use litestar-auth-guards."
---
# 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=` (or `opt={"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 through `opt={"auth": ...}`. The nearest native owner wins.
- **Keep authorization in `guards=[...]`.** Litestar's `security=` parameter is reserved for the OpenAPI requirements projected from `auth`.
- **Inject the user with `CurrentUser[T]`.** Use `NamedDependency[CurrentUser[UserType]]`; it rejects anonymous and userless service principals. `principal` and `security_context` stay typed on public routes too.
- **Authorize from the snapshot.** Guards read the `AuthorizationSnapshot` produced by the configured `authorization_resolver`. Never query the database inside a guard.
- **Compose predicates with `requires_*`.** Use `requires_any_of`, `requires_all_of`, `requires_at_least`, `requires_one_of` for predicate composition.
- **Exclude other plugins' routes by path.** Static assets and dashboards carry no `auth` and compile to implicit `required()`, so they answer `401` until listed in `SecurityConfig(exclude=[...])`. Inspect routes with `litestar security routes`.
- **Secure WebSockets with connect tokens.** Browsers cannot set handshake headers; mint a short-lived token over authenticated HTTP via `WebSocketConnectTokenService` or `WebSocketConnectTokenIssuer`.
- **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
```python
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
```python
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:
```python
@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
```python
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>
## 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](references/providers.md).
### 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.
```python
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](references/composition.md).
### Step 6: Harden the deployment
Apply `SecurityHeadersConfig.hardened()`, supply every CSP directive explicitly, and move protector keys and peppers into secret management. See [Hardening](references/hardening.md).
</workflow>
<guardrails>
## Guardrails
- **Do not pass predicates to `any_of` / `all_of` / `at_least`.** Those compose authentication mechanisms. Use `requires_any_of`, `requires_all_of`, `requires_at_least`, or `requires_one_of` for predicates.
- **Do not shadow reserved dependencies.** Avoid naming providers `principal`, `security_context`, `current_user`, or `websocket_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 or `auth=` 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 `auth` and 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_login` before 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.
</guardrails>
<validation>
## Validation Checkpoint
- [ ] `SecurityPlugin` is registered in application `plugins`.
- [ ] Every route's authentication policy is declared via `auth=` or inherited `opt={"auth": ...}`.
- [ ] Authorization uses `guards=[...]` with predicates, never the mechanism combinators.
- [ ] A custom `authorization_resolver` implements async `resolve()` and returns an `AuthorizationSnapshot`, `InvalidCredentials`, or `VerificationUnavailable`.
- [ ] No handler or guard queries the database to perform authorization checks.
- [ ] Handler injection uses `CurrentUser[UserType]` or `NamedDependency[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`, and `503` outcomes.
- [ ] Protector keys, peppers, and session secrets come from secret management.
</validation>
<example>
## Example
```python
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)],
)
```
</example>
## References Index
- **[Authentication](references/authentication.md)** — policy helpers, ownership layers, controller base classes, CSRF interaction.
- **[Authorization](references/authorization.md)** — snapshots, resolvers, predicates, combinators, tenant checks, assurance.
- **[Providers](references/providers.md)** — local accounts, OAuth/OIDC, IAP, API keys, workload JWTs, transaction protectors.
- **[Composition](references/composition.md)** — excluding routes other plugins register, and the patterns per plugin.
- **[Hardening](references/hardening.md)** — CSP, security headers, secrets, key rotation, MFA operational rules.
- **[WebSockets](references/websockets.md)** — connect tokens, close codes, snapshot refresh, revocation.
## Cross-References
- **[litestar](../litestar/SKILL.md)** — Litestar app setup and plugin list.
- **[litestar-auth-guards](../litestar-auth-guards/SKILL.md)** — native guards and low-level ASGI connection context.
- **[litestar-exceptions](../litestar-exceptions/SKILL.md)** — mapping `401` / `403` / `503` to 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>
## Shared Styleguide Baseline
- [General](../litestar-styleguide/references/general.md)
- [Python](../litestar-styleguide/references/python.md)
- [Litestar](../litestar-styleguide/references/litestar.md)