Skip to content

Litestar Integration

NeoZen provides a Litestar plugin at neozen.ext.litestar with middleware-based session management, guards, and async helpers.

Setup

from litestar import Litestar, Request, get
from litestar.middleware.session.server_side import ServerSideSessionConfig
from litestar.stores.memory import MemoryStore
from neozen.ext.litestar import (
    NeoZenConfig, NeoZenPlugin,
    get_current_user, is_authenticated,
    login_required, login_user, logout_user,
)
from neozen.storage.sqlalchemy import SessionServiceFactory, create_subject_loader

session_service = SessionServiceFactory(session_factory, SECRET_KEY)

neozen_config = NeoZenConfig(
    session_service=session_service,
    subject_loader=create_subject_loader(session_factory),
    cookie_secure=False,  # Set True in production
    login_path="/login",
)

session_config = ServerSideSessionConfig(key="session", max_age=86400 * 7)

app = Litestar(
    route_handlers=[...],
    plugins=[NeoZenPlugin(neozen_config)],
    middleware=[session_config.middleware],
    stores={"sessions": MemoryStore()},
)

Login

from litestar import post
from litestar.response import Redirect

@post("/login")
async def login_submit(request: Request) -> Response:
    form = await request.form()
    email = str(form.get("email", "")).lower()
    password = str(form.get("password", ""))

    async with session_factory() as db_session:
        repo = SubjectRepository(session=db_session)
        try:
            subject = await repo.get_one_by(email=email)
            valid, _ = hasher.verify_and_update(password, subject.hashed_password)
            if valid:
                response = Redirect(path="/dashboard")
                await login_user(request, response, subject)
                return response
        except DoesNotExist:
            pass

    flash(request, "Invalid email or password", "error")
    return render_template(request, "login.html", {"current_user": None})

Protected Routes

@get("/dashboard", guards=[login_required()])
async def dashboard(request: Request) -> Response:
    user = await get_current_user(request)
    return render_template(request, "dashboard.html", {"current_user": user})

Admin Guard

from litestar.connection import ASGIConnection
from litestar.handlers import BaseRouteHandler
from litestar.exceptions import NotAuthorizedException

def admin_required():
    async def guard(connection: ASGIConnection, _: BaseRouteHandler) -> None:
        user = await get_current_user(connection)
        if not user:
            raise NotAuthorizedException("Admin access required")
        roles = await get_subject_roles(session_factory, user.id)
        if "admin" not in roles:
            raise NotAuthorizedException("Admin access required")
    return guard

@get("/admin", guards=[login_required(), admin_required()])
async def admin_dashboard(request: Request) -> Response:
    ...

Logout

@post("/logout")
async def logout_submit(request: Request) -> Response:
    response = Redirect(path="/")
    await logout_user(request, response)
    return response

CSRF Protection

NeoZen's session cookie is SameSite=Lax, which stops most cross-site form posts but not all of them (older browsers, SameSite=None deployments). The plugin doesn't own your login, logout or settings forms, so turn on Litestar's CSRF middleware:

from litestar.config.csrf import CSRFConfig

app = Litestar(..., csrf_config=CSRFConfig(secret=SECRET_KEY, cookie_secure=True))

With Litestar's template engine, put {{ csrf_input | safe }} in each form; HTMX and JavaScript send the token in the x-csrftoken header instead. The OAuth consent form is the exception: NeoZen protects it itself, with a consent_token (see Consent Form).

Direct Factor Wiring

If you own your User model and don't want to use NeoZen's middleware, you can wire factors directly:

from neozen.core.factors.password import create_password_factor
from neozen.core.identifiers.email import create_email_identifier

# In your login handler:
identifier = create_email_identifier(my_user_repo_adapter)
factor = create_password_factor(identifier, my_user_repo_adapter, hasher)
user = await factor.authenticate({"identifier": email, "password": password})

See Bring Your Own Model for details.

Demo Application

A complete Litestar demo is at demos/litestar/ in the repository.