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").
api_key(*, verify, locations, header_name, query_name, cookie_name)¶
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".
session_cookie(*, store, secret, cookie_name, user_builder, realm)¶
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¶
csrf(*, cookie_name, header_name, safe_methods)¶
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.
| 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¶
Related guides¶
- Getting started — end-to-end example.
- Bearer Token · Basic Auth · API Key
- JWT · OAuth2 Introspection · Session Cookie
- Authorization · CSRF · IP Allowlist
- Sessions · Passwords · Public routes