Skip to content

Types and protocols

Runtime-checkable Protocol definitions and type aliases used across transports, strategies, and user models.

DbSessionDependencyKey is the public type alias for LitestarAuthConfig.db_session_dependency_key. It is an Annotated[str, ...] contract: values must be valid non-keyword Python identifiers because Litestar matches dependency keys to callable parameter names. The default is "db_session".

RoleCapableUserProtocol is the dedicated public typing surface for user objects that expose normalized flat roles membership. Role and permission guards use this shape before checking authorization grants. Use that protocol directly instead of treating an arbitrary roles attribute as sufficient.

That protocol describes the library boundary rather than a storage implementation detail. Bundled SQLAlchemy models store role membership in relational role / user_role tables; managers, schemas, and guards still exchange one normalized flat roles collection.

Which protocol should my user model implement?

Pick the narrowest protocol that covers the features you enable. If a guard or TOTP flow needs attributes your model does not expose, authentication may succeed while authorization fails with a generic permission error—so align the model with Guards and TOTP usage up front.

If you use… Implement
Basic authentication only (strategies resolve request.user by id) UserProtocol or UserProtocolStrictid
is_active, is_verified guards GuardedUserProtocolis_active, is_verified
is_superuser, role guards, and permission guards guards RoleCapableUserProtocolroles
TOTP enrollment, verification, or 2FA flows TotpUserProtocolemail, totp_secret

Use UserProtocol when code needs isinstance(user, UserProtocol) at runtime. Use UserProtocolStrict for static-only annotations and type-variable bounds. The strict variant avoids the @runtime_checkable tradeoff: runtime protocol checks are convenient but only validate the runtime-visible shape of the protocol and carry extra overhead.

Config inference and dependency-key typing

Construct LitestarAuthConfig directly. For strict typing, spell both generic parameters as LitestarAuthConfig[User, UUID](...) so the configured user and ID types stay explicit.

The db_session_dependency_key parameter is typed as DbSessionDependencyKey, so the annotation documents the same identifier rule enforced at runtime. This complete example type-checks as LitestarAuthConfig[User, UUID]:

from dataclasses import dataclass
from typing import assert_type
from uuid import UUID

from litestar_auth import LitestarAuthConfig
from litestar_auth.types import DbSessionDependencyKey
from litestar_auth.manager import BaseUserManager
from litestar_auth.types import UserProtocolStrict


@dataclass(slots=True)
class User:
    id: UUID
    email: str


class UserManager(BaseUserManager[User, UUID]):
    pass


def needs_static_user_contract(user: UserProtocolStrict[UUID]) -> UUID:
    return user.id


dependency_key: DbSessionDependencyKey = "db_session"

config = LitestarAuthConfig[User, UUID](
    user_model=User,
    user_manager_class=UserManager,
    db_session_dependency_key=dependency_key,
)

assert_type(config, LitestarAuthConfig[User, UUID])

Example: account state and roles together

A single model often needs GuardedUserProtocol and RoleCapableUserProtocol so both account-state and role guards can run:

from collections.abc import Sequence
from dataclasses import dataclass


@dataclass(slots=True)
class StaffUser:
    """Satisfies GuardedUserProtocol and RoleCapableUserProtocol."""

    id: int
    is_active: bool
    is_verified: bool
    roles: Sequence[str]

Add TOTP fields when you enable 2FA; see TotpUserProtocol in the generated API below.

Permission resolver protocol

Permission authorization is resolved through a structural protocol on LitestarAuthConfig.permission_resolver:

from typing import Protocol


class PermissionResolver(Protocol):
    def resolve(self, user: object, *, context: object | None = None) -> frozenset[str]:
        ...

The protocol is exported for explicit typing and structural conformance as from litestar_auth import PermissionResolver; the bundled default implementation is available as from litestar_auth import StaticRolePermissionResolver. Because PermissionResolver is a structural protocol you do not have to subclass it — any object with a compatible resolve() method satisfies permission_resolver.

You normally do not need to instantiate this protocol directly. Configure LitestarAuthConfig.role_permissions for static role-derived permissions, or pass any object with a compatible resolve() method as permission_resolver when permissions come from another source.

from dataclasses import dataclass
from uuid import UUID

from litestar_auth import LitestarAuthConfig
from litestar_auth.models import User


@dataclass(slots=True)
class StaticAuditResolver:
    def resolve(self, user: object, *, context: object | None = None) -> frozenset[str]:
        roles = set(getattr(user, "roles", ()))
        if "auditor" in roles:
            return frozenset({"reports:read"})
        return frozenset()


config = LitestarAuthConfig[User, UUID](
    user_model=User,
    user_manager_class=UserManager,
    session_maker=session_maker,
    backends=[jwt_backend],
    permission_resolver=StaticAuditResolver(),
)

Resolver output is normalized and validated before guards or the litestar_auth_permissions dependency consume it. Invalid output fails closed with a permission-denied response. When permission_resolver is set, it takes precedence over role_permissions; otherwise the plugin builds the static role-permission resolver from role_permissions.

The context keyword is reserved for request-aware resolution. The plugin passes the current Litestar connection today, and the static resolver intentionally ignores it. Treat context=None as a supported call shape for app-owned tests and helper code. Multi-tenant and DB-backed permission lookup can use this argument in custom resolvers, but built-in durable multi-tenant authorization semantics remain future work rather than a promised release date.

litestar_auth.types

Shared typing primitives and authentication protocols.

Protocol selection (abbreviated)

Implement the narrowest protocol that matches the features you use:

Feature area Required protocol
Basic authentication (identity in strategies, User typing) UserProtocolid
is_active, is_verified guards GuardedUserProtocol — account state booleans
is_superuser, has_any_role, has_all_roles guards RoleCapableUserProtocol — flat roles
TOTP enrollment / verification TotpUserProtocolemail, totp_secret

UserProtocol remains @runtime_checkable so runtime guard code can use isinstance(...). Use UserProtocolStrict for static-typing-only contracts where no runtime check is needed; avoiding @runtime_checkable keeps the protocol's stricter static intent clear and avoids protocol runtime-check overhead.

The full decision table, guard cross-links, and a multi-protocol model example are in docs/api/types.md (API → Types in the built docs).

ContextualStrategyProtocol

Bases: StrategyProtocol[UP, ID], Protocol[UP, ID]

Protocol for strategies that expose custom request authentication context.

read_token_with_context(token, user_manager) async

Resolve a user plus strategy-specific request auth context.

Source code in litestar_auth/types.py
async def read_token_with_context(self, token: str | None, user_manager: object) -> object | None:
    """Resolve a user plus strategy-specific request auth context."""

GuardedUserProtocol

Bases: UserProtocol[ID], Protocol[ID]

Protocol for user models that support account-state guards.

PermissionResolver

Bases: Protocol

Extension point for resolving a user's effective permissions.

Implement this protocol to supply LitestarAuthConfig.permission_resolver with a custom permission source (for example a database-backed resolver). The optional context argument is reserved for future multi-tenancy support; the bundled :class:~litestar_auth.StaticRolePermissionResolver ignores it.

resolve(user, *, context=None)

Return the effective permission tokens granted to user.

Source code in litestar_auth/types.py
def resolve(self, user: object, *, context: object | None = None) -> frozenset[str]:
    """Return the effective permission tokens granted to ``user``."""
    ...

RoleCapableUserProtocol

Bases: UserProtocol[ID], Protocol[ID]

Protocol for user models that expose normalized flat role membership.

StrategyProtocol

Bases: Protocol[UP, ID]

Protocol describing how auth tokens map to users.

destroy_token(token, user) async

Invalidate a token for strategies that keep server-side state.

Source code in litestar_auth/types.py
async def destroy_token(self, token: str, user: UP) -> None:
    """Invalidate a token for strategies that keep server-side state."""

read_token(token, user_manager) async

Resolve a user from a transport token.

Source code in litestar_auth/types.py
async def read_token(self, token: str | None, user_manager: object) -> UP | None:
    """Resolve a user from a transport token."""

write_token(user) async

Create a token for a given user.

Source code in litestar_auth/types.py
async def write_token(self, user: UP) -> str:
    """Create a token for a given user."""

TotpUserProtocol

Bases: UserProtocol[ID], Protocol[ID]

Protocol for user models that support TOTP 2FA.

TransportProtocol

Bases: Protocol

Protocol describing how auth tokens move in and out of requests.

read_token(connection) async

Extract a login token from an incoming connection.

Source code in litestar_auth/types.py
async def read_token(self, connection: ASGIConnection[Any, Any, Any, Any]) -> str | None:
    """Extract a login token from an incoming connection."""

set_login_token(response, token)

Persist a login token on an outgoing response.

Source code in litestar_auth/types.py
def set_login_token(self, response: Response[Any], token: str) -> Response[Any]:
    """Persist a login token on an outgoing response."""

set_logout(response)

Clear any transport-specific authentication state.

Source code in litestar_auth/types.py
def set_logout(self, response: Response[Any]) -> Response[Any]:
    """Clear any transport-specific authentication state."""

UserProtocol

Bases: UserProtocolStrict[ID], Protocol[ID]

Runtime-checkable protocol for user models handled by the library.

This variant supports isinstance(...) checks, which are convenient at runtime but slower and less precise than static protocol validation. Use :class:UserProtocolStrict when runtime checks are unnecessary.

UserProtocolStrict

Bases: Protocol[ID]

Static-only protocol for user models handled by the library.

Prefer this variant for annotations and TypeVar bounds that do not need isinstance(...) checks.