Auth gating (login_required)
Gate handlers and Router subtrees behind authentication with LoggedIn<U> and LoginRequiredLayer.
Two complementary shapes for requiring an authenticated user: an axum extractor for per-handler gating and a tower::Layer middleware for gating whole Router subtrees. Both ship from umbral-auth. Both share one LoginRequired config that picks between two error responses - JSON 401 for APIs, 302 redirect for HTML pages.
The full reference lives under the auth plugin: Guarding views.
The two shapes
use umbral_auth::{AuthUser, login_required::{LoggedIn, LoginRequired, login_required, login_required_html}};use umbral::web::{Router, get, Json, Html}; // 1. Extractor - per-handler. Handler only runs on a valid session.async fn me(LoggedIn(user): LoggedIn<AuthUser>) -> Json<serde_json::Value> { Json(serde_json::json!({ "username": user.username }))} // 2. Layer - per-Router. Gates every route under it.let api = Router::new() .route("/api/me", get(me)) .route("/api/posts", get(list_posts)) .layer(login_required()); // 401 JSON on unauthenticated let app = Router::new() .route("/dashboard", get(dashboard)) .route("/settings", get(settings_page)) .layer(login_required_html("/login")); // 302 to /login?next=<uri>When to use which
| Shape | Reach for it when ... |
|---|---|
LoggedIn<U> extractor | One handler needs the authenticated user. The extractor is also what reads user.id or user.is_staff inside the handler body. |
LoginRequiredLayer | A whole router subtree (/admin/*, /app/*) should require login. The layer rejects unauthenticated requests before they reach the handler. |
| Both together | The layer does the broad gate; the extractor inside the handler pulls out the user struct without re-authenticating. The layer's config flows to the extractor through request extensions. |
API vs HTML response shape
LoginRequired::API (the default, returned by login_required()) responds with a 401 JSON envelope plus WWW-Authenticate: Bearer. LoginRequired::html("/login") (returned by login_required_html(...)) responds with a 302 to /login?next=<original-uri>. Drop the ?next= with .no_next() if your login page does not use it.
Mix both in one app: wrap /api/* with login_required() and /app/* with login_required_html("/login").
Generic over the user model
LoggedIn<U> is generic over any U: UserModel, so a custom user model works without adapters:
async fn tenant_home(LoggedIn(user): LoggedIn<TenantUser>) -> Html<String> { Html(format!("<h1>{} - tenant {}</h1>", user.username, user.tenant_id))}See also
umbral-authplugin docs - full reference forLoginRequired,LoggedIn<U>,LoginRequiredLayer.- Users and passwords - the underlying user / session story.
- Sessions - how the session cookie is created and validated.