Skip to content

lauren-guards

Batteries-included authentication and authorization guards for the Lauren web framework.

Every production service eventually needs the same cross-cutting auth concerns. lauren-guards ships them all as first-class Lauren guards so you can wire them in with a single decorator. The full site lives at https://lauren-framework.github.io/lauren-guards/.


What's inside

  • Authentication — Bearer token, Basic Auth, API key, JWT (RS256/HS256/ES256), OAuth2 introspection, session cookie.
  • Authorizationrequire_authenticated, require_roles, require_scopes.
  • Cross-cutting — CSRF (double-submit-cookie), IP allowlist.
  • UtilitiesBcryptHasher, Argon2Hasher, generate_token, InMemorySessionStore.
  • Public-route bypass@public decorator and IS_PUBLIC_KEY for opt-out routes.
  • DI-managed — every guard factory returns an @injectable(scope=SINGLETON) class; the Lauren DI container owns its lifecycle.
  • Composable — use guards at the global, controller, or route level; combine multiple guards with @use_guards(GuardA, GuardB).

Installation

# Core guards (Bearer, Basic, API key, session cookie, CSRF, IP)
pip install lauren-guards

# JWT support (RS256/HS256/ES256)
pip install "lauren-guards[jwt]"

# OAuth2 token introspection
pip install "lauren-guards[http]"

# Password hashing — bcrypt
pip install "lauren-guards[bcrypt]"

# Password hashing — Argon2
pip install "lauren-guards[argon2]"

# Everything
pip install "lauren-guards[all]"

Quick Start

from lauren import LaurenFactory, controller, get, module, use_guards
from lauren_guards import bearer_token, require_scopes, AuthUser

async def verify_token(token: str) -> AuthUser | None:
    """Return an AuthUser if the token is valid, None otherwise."""
    if token == "secret":
        return AuthUser(id="user-1", scopes=("read", "write"))
    return None

BearerGuard = bearer_token(verify=verify_token)

@use_guards(BearerGuard)
@controller("/api")
class ApiController:
    @get("/profile")
    @use_guards(require_scopes("read"))
    async def profile(self) -> dict:
        return {"ok": True}

@module(controllers=[ApiController])
class AppModule:
    pass

app = LaurenFactory.create(AppModule)

Take the getting started guide for a longer walkthrough, or jump straight to the API reference.


Guard architecture

All guards follow Lauren's GuardProtocol:

class GuardProtocol(Protocol):
    async def can_activate(self, ctx: ExecutionContext) -> bool: ...

Guards can either return False (yields 403) or raise an HTTPError subclass (e.g., UnauthorizedError for 401) to control the exact response.

The AuthUser principal record is set on request.state.user after successful authentication:

@injectable(scope=Scope.SINGLETON)
class MyGuard:
    async def can_activate(self, ctx: ExecutionContext) -> bool:
        user = ctx.request.state.get("user")
        return user is not None

Explore

Questions? Open an issue at github.com/lauren-framework/lauren-guards.