Skip to content

API Reference

Complete reference for the public symbols exported from lauren_guards.

All guard factories return an @injectable(scope=SINGLETON) class whose can_activate(ctx) implements Lauren's GuardProtocol. Pass the result directly to @use_guards(...).


Authentication Guards

bearer_token(*, verify, realm, error_description)

Validates HTTP Bearer tokens (RFC 6750) with a caller-supplied verifier.

def bearer_token(
    *,
    verify: Callable[[str], AuthUser | None] | Callable[[str], Awaitable[AuthUser | None]],
    realm: str = "lauren",
    error_description: bool = False,
) -> type:
    ...
Parameter Type Default Description
verify callable Sync or async (token) -> AuthUser \| None.
realm str "lauren" Realm surfaced in error responses / challenge handlers.
error_description bool False Include a short reason (missing_token, malformed_header, invalid_token, verifier_error) in the UnauthorizedError detail.

basic_auth(*, verify, realm)

HTTP Basic authentication (RFC 7617).

def basic_auth(
    *,
    verify: Callable[[str, str], AuthUser | None],
    realm: str = "lauren",
) -> type:
    ...
Parameter Type Default Description
verify callable Sync or async (username, password) -> AuthUser \| None.
realm str "lauren" Realm used by basic_auth_challenge_handler.

basic_auth_challenge_handler

An @exception_handler(UnauthorizedError) that attaches WWW-Authenticate: Basic realm="...", charset="UTF-8" to every 401 response, so browsers show their native credential dialog. Register it in global_exception_handlers. It reads the realm stashed on request.state by the guard (falling back to "lauren").

Validates API keys from configurable locations.

def api_key(
    *,
    verify: Callable[[str], AuthUser | None],
    locations: Iterable[KeyLocation] = ("header",),   # "header" | "query" | "cookie"
    header_name: str = "x-api-key",
    query_name: str = "api_key",
    cookie_name: str = "api_key",
) -> type:
    ...

The first configured location that yields a non-empty value wins; the key is passed to verify. Storing keys in query strings writes them to access logs at every hop — prefer header or cookie.

jwt_bearer(...)

Validates JSON Web Tokens (HS256/HS384/HS512, RS256/RS384/RS512, ES256/ES384/ES512), optionally fetching keys from a JWKS endpoint.

Requires: pip install "lauren-guards[jwt]" (plus cryptography for RS/ES, and httpx for jwks_url).

def jwt_bearer(
    *,
    secret: str | bytes | None = None,
    public_key: str | bytes | None = None,
    jwks_url: str | None = None,
    algorithms: Iterable[str] = ("HS256",),
    issuer: str | None = None,
    audience: str | Iterable[str] | None = None,
    leeway: int = 0,
    sub_claim: str = "sub",
    role_claim: str | Callable[[dict], Iterable[str]] | None = None,
    scope_claim: str | Callable[[dict], Iterable[str]] | None = "scope",
    jwks_cache_seconds: int = 300,
    realm: str = "lauren",
) -> type:
    ...

Provide exactly one of secret / public_key / jwks_url.

Parameter Type Default Description
secret str \| bytes \| None None Symmetric key for HMAC algorithms (HS256/HS384/HS512).
public_key str \| bytes \| None None Public key (PEM) for RS/ES verification.
jwks_url str \| None None Fetch the issuer's JWKS document, pick the key matching kid, cache for jwks_cache_seconds.
algorithms iterable ("HS256",) Allowed algorithms. The JWT's alg header must be in this list.
issuer str \| None None Required iss claim when set.
audience str \| Iterable[str] \| None None Required aud claim(s).
leeway int 0 Clock-skew tolerance (seconds) for exp/nbf/iat.
sub_claim str "sub" Claim whose value becomes AuthUser.id.
role_claim str \| callable \| None None Where to read roles. Callable (claims) -> Iterable[str] for nested paths.
scope_claim str \| callable \| None "scope" Where to read scopes (space-separated string or list).
jwks_cache_seconds int 300 TTL for the cached JWKS document.
realm str "lauren" Surfaced in error responses.

AuthUser.id is claims[sub_claim]; the full decoded payload is preserved in user.claims; credential_type is "jwt". secret= only supports HMAC algorithms — combining secret with RS/ES raises ValueError.

oauth2_introspection(...)

Validates opaque tokens via RFC 7662 introspection.

Requires: pip install "lauren-guards[http]".

def oauth2_introspection(
    *,
    introspection_url: str,
    client_id: str,
    client_secret: str,
    sub_claim: str = "sub",
    role_claim: str | None = None,
    scope_claim: str = "scope",
    cache_seconds: int = 60,
    issuer: str | None = None,
    audience: str | Iterable[str] | None = None,
    realm: str = "lauren",
    timeout_seconds: float = 5.0,
) -> type:
    ...
Parameter Type Default Description
introspection_url str Auth server's introspection endpoint.
client_id, client_secret str Resource server credentials (Basic auth on the introspection call).
sub_claim str "sub" Response field used as AuthUser.id.
role_claim str \| None None Response field for roles.
scope_claim str "scope" Response field for scopes.
cache_seconds int 60 Per-token cache TTL (0 disables caching).
issuer str \| None None Required iss when set.
audience str \| Iterable[str] \| None None Required aud when set.
realm str "lauren" Surfaced in error responses.
timeout_seconds float 5.0 HTTP timeout for the introspection call.

The response's active field must be True; credential_type is "oauth2".

Loads sessions from a signed cookie.

def session_cookie(
    *,
    store: SessionStore,
    secret: str | bytes,
    cookie_name: str = "lauren_session",
    user_builder: Callable[[Session], AuthUser | None] | None = None,
    realm: str = "lauren",
) -> type:
    ...
Parameter Type Default Description
store SessionStore Session backend (e.g. InMemorySessionStore).
secret str \| bytes HMAC key for the cookie signature.
cookie_name str "lauren_session" Cookie to read.
user_builder callable | None None (Session) -> AuthUser \| None. Default reads session.user_id and session.data["roles"]/["scopes"].
realm str "lauren" Surfaced in error responses.

credential_type is "session".


Authorization Guards

These guards read request.state.user (set by an upstream authentication guard) and never validate credentials themselves. If no AuthUser is present they raise UnauthorizedError (401); on an authorization failure they raise ForbiddenError (403).

require_authenticated()

Builds a guard that allows the request iff request.state.user is set. It is a factory — call it like the others:

@use_guards(bearer_token(verify=...), require_authenticated())
@controller("/billing")
class BillingController: ...

require_roles(*roles, require_all=False)

Builds a guard that requires the user to have the given roles.

Parameter Type Default Description
*roles str One or more role names. At least one required.
require_all bool False False (default) = at least one role (OR); True = all roles (AND).
@use_guards(require_roles("admin"))                      # must have 'admin'
@use_guards(require_roles("admin", "billing.manager"))   # either (OR)
@use_guards(require_roles("admin", "audit.confirm", require_all=True))  # both

Raises ForbiddenError when the check fails.

require_scopes(*scopes, require_all=True)

Builds a guard that requires the user's OAuth scopes.

Parameter Type Default Description
*scopes str One or more scope strings. At least one required.
require_all bool True True (default) = all scopes (AND); False = at least one (OR).

Scopes are read from AuthUser.scopes, populated by jwt_bearer (from the scope claim), oauth2_introspection (from the scope field), and session_cookie (from session.data["scopes"]).


Cross-Cutting Guards

Double-submit-cookie CSRF protection.

def csrf(
    *,
    cookie_name: str = "lauren_csrf",
    header_name: str = "x-csrf-token",
    safe_methods: Iterable[str] = ("GET", "HEAD", "OPTIONS", "TRACE"),
) -> type:
    ...

Safe methods are exempt. For state-changing methods the cookie and header must both be present and match (constant-time comparison) or ForbiddenError (403) is raised.

ip_allowlist(*, allow, trusted_proxies)

Restricts access to clients whose IP falls inside configured CIDR ranges.

def ip_allowlist(
    *,
    allow: Iterable[str],
    trusted_proxies: Iterable[str] = (),
) -> type:
    ...
Parameter Type Default Description
allow iterable CIDR ranges / bare IPs (e.g. "10.0.0.0/8", "203.0.113.42"). At least one required.
trusted_proxies iterable () CIDR ranges of your proxies. When set, X-Forwarded-For is honoured (rightmost untrusted hop wins). Empty disables the header.
ip_allowlist(allow=["10.0.0.0/8", "127.0.0.1"])
ip_allowlist(allow=["10.0.0.0/8"], trusted_proxies=["100.64.0.0/10"])

Principal Record

AuthUser

The authenticated principal written to request.state.user by every authentication guard. Frozen-style slots dataclass:

@dataclass(slots=True)
class AuthUser:
    id: str
    roles: tuple[str, ...] = ()
    scopes: tuple[str, ...] = ()
    claims: dict[str, Any] = field(default_factory=dict)
    credential_type: str = "unknown"

Helper methods: has_role(r), has_any_role(*rs), has_all_roles(*rs), has_scope(s), has_all_scopes(*ss).

get_user(request) / set_user(request, user)

Explicit state helpers to read/write the principal on request.state.user:

from lauren_guards import get_user, set_user

user = get_user(request)         # AuthUser | None
set_user(request, auth_user)     # write it back

Cookies & Sessions

SessionStore (Protocol)

class SessionStore(Protocol):
    async def create(
        self,
        *,
        user_id: str,
        data: dict[str, Any] | None = None,
        ttl_seconds: int | None = None,
    ) -> Session: ...
    async def get(self, session_id: str) -> Session | None: ...
    async def delete(self, session_id: str) -> None: ...

Session

@dataclass(slots=True)
class Session:
    id: str
    user_id: str
    data: dict[str, Any] = field(default_factory=dict)
    created_at: float = field(default_factory=time.time)
    expires_at: float | None = None

InMemorySessionStore

Process-local SessionStore. Fine for development and single-worker deployments; use a shared (e.g. Redis) store in multi-worker production. Sessions expire lazily at read time; get on an expired session removes it and returns None.

sign_cookie(session_id, *, secret) -> str

Returns "<id>.<hex-hmac-sha256>". Raises ValueError if the id contains ..

verify_cookie(value, *, secret) -> str | None

Constant-time HMAC verification. Returns the session id on success, None on tamper/format errors (does not raise).


Password Hashing

PasswordHasher (Protocol)

class PasswordHasher(Protocol):
    def hash(self, password: str) -> str: ...
    def verify(self, password: str, hashed: str) -> bool: ...

verify returns False (never raises) on mismatch or malformed hashes.

BcryptHasher(*, rounds=12)

Requires: pip install "lauren-guards[bcrypt]". rounds must be 4–31.

Argon2Hasher(*, time_cost=2, memory_cost=19456, parallelism=1, hash_len=32, salt_len=16)

Requires: pip install "lauren-guards[argon2]". Defaults follow the OWASP cheat sheet.

generate_token(length=32) -> str

secrets.token_urlsafe-based, URL-safe base64 token of roughly length characters. Raises ValueError for length < 8. Useful for session ids, API keys, CSRF tokens.


Public Routes

IS_PUBLIC_KEY

Metadata key constant: "lauren-guards.authentication.is_public".

@public

Route decorator that sets IS_PUBLIC_KEY = True and wraps the handler with NullGuard. Guards in this package check the metadata and skip authentication for the decorated route.


Version

from lauren_guards import __version__

print(__version__)