litestar-org/litestar-skills/litestar-security

Claude Code and agent skill by litestar-org/litestar-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Auto-activate for litestar_security, SecurityPlugin, SecurityConfig, SecureController, CurrentUser, Principal, SecurityContext, or requires_role/scope/tenant/capability/assurance. Not for basic Litestar Guards.

Install

npx vskill@latest install litestar-org/litestar-skills/litestar-security

SKILL.md

Litestar Security

litestar-security 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 `SecureController`, `PublicController`, or `auth=`. Subclass SecureController (auth: ClassVar[AuthenticationPolicy] = required(...)) or PublicController for controllers, pass auth= on individual routes, and use opt={"auth": ...} on routers or third-party controllers. The nearest native owner wins. When a public controller needs native Litestar CSRF on cookie-session POST routes, subclass SecureController with auth: ClassVar[AuthenticationPolicy] = exclude() and set opt={"exclude_from_csrf": False} on the POST handlers (or set opt={"csrf_required": True} on individual handlers) because public() sets exclude_from_csrf=True and rejects opt={"exclude_from_csrf": False}.
  • Keep authorization in `guards=[...]`. Litestar's security= parameter is reserved for the OpenAPI requirements projected from auth.
  • Supply `scheme_name` and `security_scheme` on custom mechanisms. Every AuthenticationMechanism in SecurityConfig(mechanisms=(...,)) must set both scheme_name and security_scheme=SecurityScheme(...) when OpenAPI is enabled, or startup fails with ImproperlyConfiguredException.
  • Inject the user with `CurrentUser[T]`. Annotate current_user: CurrentUser[UserType] (already a NamedDependency[UserType] alias); it rejects anonymous and userless service principals. principal and security_context stay typed on public routes too via NamedDependency.
  • 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, never OpenAPI or `.well-known`. Static assets and dashboards carry no auth and compile to implicit required(), so they answer 401 until listed in SecurityConfig(exclude=[...]). Litestar's OpenAPI routes (/schema, /docs) and ProtectedResourceConfig / LocalJWKSConfig routes are already public() — do not add them to 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 typing import ClassVar
from litestar import get
from litestar_security import (
AuthenticationPolicy,
PublicController,
SecureController,
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 policy on controllers via SecureController / PublicController and on routes via auth=:

python
class HealthController(PublicController):
path = "/health"
@get("/")
async def check(self) -> dict[str, str]:
return {"status": "ok"}
class AccountController(SecureController):
path = "/accounts"
auth: ClassVar[AuthenticationPolicy] = required("session")

Use opt={"auth": ...} only on Router, Litestar, or third-party controllers that cannot subclass SecureController.

Authorization Guards
python
from typing import ClassVar
from litestar import get
from litestar_security import (
AuthenticationPolicy,
SecureController,
required,
requires_any_of,
requires_role,
requires_scope,
)
class ReportsController(SecureController):
path = "/reports"
auth: ClassVar[AuthenticationPolicy] = 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.

KeyTypeUse
principalNamedDependency[Principal[User]]Stable envelope identity plus the active user model
security_contextNamedDependency[SecurityContext]Active session, evidence, snapshot, and restrictions
current_userCurrentUser[User]Narrowing shortcut (NamedDependency[User]); rejects anonymous and service principals
csp_noncecsp_nonce (NamedDependency[str])Per-request CSP nonce when ContentSecurityPolicy.nonce_directives is non-empty
websocket_connect_tokensNamedDependency[WebSocketConnectTokenIssuer]Route-aware WebSocket connect-token issuer
Status Code Contract
OutcomeStatus
Authentication failure401
Guard denial403
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.

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.

Step 6: Harden the deployment

Apply SecurityHeadersConfig.hardened(), supply every CSP directive explicitly, and move protector keys and peppers into secret management. See Hardening.

</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, csp_nonce, 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 set `opt={"exclude_from_csrf": False}` on `PublicController` (`auth = public()`) routes. public() sets exclude_from_csrf=True and RouteCompiler._validate_csrf_metadata raises ImproperlyConfiguredException: Native CSRF exclusions must be exactly True. For a public controller whose cookie-session POST routes require native Litestar CSRF, set auth: ClassVar[AuthenticationPolicy] = exclude() on a SecureController subclass and opt={"exclude_from_csrf": False} on the POST handlers (or set opt={"csrf_required": True} on individual HTTP handlers).
  • Do not omit `scheme_name` and `security_scheme` on custom `AuthenticationMechanism` instances. When OpenAPI is enabled, OpenAPISchemeSet.from_registry() raises ImproperlyConfiguredException: Authentication mechanism <name> has no native OpenAPI security scheme.
  • Do not put a layer-level policy above excluded routes, or add `/docs`, `/schema`, or `/.well-known` to `SecurityConfig.exclude`. A route that both declares auth and matches an exclusion pattern is rejected at startup, and OpenAPI routes are already auto-detected as public().
  • Do not put bearer credentials in WebSocket query strings. Use a connect token or an HttpOnly cookie.
  • Do not enable `MFAConfig(require_at_login=True)` before enrolling factors. Unenrolled accounts lock themselves out; use require_at_login="enrolled" to enforce login MFA only for accounts that have enrolled a factor.
  • 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

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
15
7-day trend score
14

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/litestar-org/litestar-skills/litestar-security/badge)](https://verified-skill.com/skills/litestar-org/litestar-skills/litestar-security)

Something wrong with this skill or its page? Tell us.