### Axum-Login Builder Quick Start Source: https://github.com/maxcountryman/axum-login/blob/main/README.md This example demonstrates the basic setup for axum-login's builder pattern, including defining a User type, an Authentication Backend, and configuring the Require middleware with a redirect handler for unauthenticated users. ```rust use axum_login::require::{RedirectHandler, Require}; use axum_login::{AuthUser, AuthnBackend, UserId}; #[derive(Clone, Debug)] struct User; impl AuthUser for User { type Id = i64; fn id(&self) -> Self::Id { 0 } fn session_auth_hash(&self) -> &[u8] { &[] } } #[derive(Clone)] struct Backend; impl AuthnBackend for Backend { type User = User; type Credentials = (); type Error = std::convert::Infallible; async fn authenticate( &self, _: Self::Credentials, ) -> Result, Self::Error> { Ok(Some(User)) } async fn get_user( &self, _: &UserId, ) -> Result, Self::Error> { Ok(Some(User)) } } let require = Require::::builder() .unauthenticated(RedirectHandler::new().login_url("/login")) .build(); ``` -------------------------------- ### Basic Require Builder Setup Source: https://context7.com/maxcountryman/axum-login/llms.txt Configures a `Require` middleware to redirect unauthenticated users to '/login' and return a 403 Forbidden for unauthorized access. ```rust use std::sync::Arc; use axum::{http::StatusCode, response::IntoResponse, routing::get, Router}; use axum_login::{ require::{Decision, PermissionMatch, PermissionsPredicate, RedirectHandler, Require, SimpleResponseHandler}, AuthSession, }; // ── Basic: redirect unauthenticated, 403 for unauthorized ────────────────── let require = Require::::builder() .unauthenticated(RedirectHandler::new().login_url("/login")) .unauthorized(SimpleResponseHandler::text(StatusCode::FORBIDDEN, "Access denied")) .build(); ``` -------------------------------- ### AuthManagerLayerBuilder Setup Source: https://context7.com/maxcountryman/axum-login/llms.txt Illustrates how to set up the authentication service by combining a custom backend with a `tower-sessions` `SessionManagerLayer` using `AuthManagerLayerBuilder`. This layer should be added as the outermost layer to the Axum router. ```APIDOC ## `AuthManagerLayerBuilder` — Setting Up the Auth Service Combines your backend and a `tower-sessions` `SessionManagerLayer` into an Axum `Layer`. Add it last on the router so it wraps all routes. ```rust use axum::{routing::{get, post}, Router}; use axum_login::{ login_required, tower_sessions::{MemoryStore, SessionManagerLayer}, AuthManagerLayerBuilder, }; use tokio::net::TcpListener; #[tokio::main] async fn main() -> Result<(), Box> { // 1. Session store (use a persistent store like SqliteStore in production). let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store); // 2. Instantiate your backend (wraps your DB pool, etc.). let backend = Backend::new(db_pool); // 3. Build the auth layer; optionally customise the session data key. let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer) .with_data_key("myapp.auth") // default: "axum-login.data" .build(); // 4. Wire routes — protected routes go above the auth layer. let app = Router::new() .route("/dashboard", get(dashboard_handler)) .route_layer(login_required!(Backend, login_url = "/login")) .route("/login", get(login_page)) .route("/login", post(login_submit)) .route("/logout", get(logout_handler)) .layer(auth_layer); // <-- must be the outermost layer let listener = TcpListener::bind("0.0.0.0:3000").await?; axum::serve(listener, app.into_make_service()).await?; Ok(()) } ``` ``` -------------------------------- ### Implement AuthzBackend for Permissions Source: https://context7.com/maxcountryman/axum-login/llms.txt Extend `AuthzBackend` to add user and group permission support. Override `get_user_permissions` and `get_group_permissions` to fetch permissions from your data source. Default implementations for `get_all_permissions` and `has_perm` merge these sets. ```rust use std::collections::HashSet; use axum_login::AuthzBackend; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Permission { pub name: String, } impl From<&str> for Permission { fn from(s: &str) -> Self { Permission { name: s.to_string() } } } impl AuthzBackend for Backend { type Permission = Permission; async fn get_user_permissions( &self, user: &Self::User, ) -> Result, Self::Error> { let rows: Vec<(String,)> = sqlx::query_as("SELECT permission FROM user_permissions WHERE user_id = ?") .bind(user.id()) .fetch_all(&self.db) .await?; Ok(rows.into_iter().map(|(name,)| Permission { name }).collect()) } async fn get_group_permissions( &self, user: &Self::User, ) -> Result, Self::Error> { let rows: Vec<(String,)> = sqlx::query_as( "SELECT gp.permission FROM group_permissions gp JOIN user_groups ug ON ug.group_id = gp.group_id WHERE ug.user_id = ?" ) .bind(user.id()) .fetch_all(&self.db) .await?; Ok(rows.into_iter().map(|(name,)| Permission { name }).collect()) } } // Usage: check a single permission async fn check(backend: &Backend, user: &User) { let allowed = backend.has_perm(user, Permission::from("admin.read")).await.unwrap(); // get_all_permissions merges user + group permissions let all = backend.get_all_permissions(user).await.unwrap(); println!("admin.read: {allowed}, all perms: {all:?}"); } ``` -------------------------------- ### url_with_redirect_query Helper Source: https://context7.com/maxcountryman/axum-login/llms.txt Shows how to use the `url_with_redirect_query` utility function to construct login URLs with appropriate redirect parameters, handling existing query strings. ```APIDOC ## `url_with_redirect_query` — Redirect URL Helper Internal utility (publicly exported) that appends a URL-encoded `next` (or custom) query parameter to a login URL, preserving any existing query string and respecting an explicit redirect already present. ```rust use axum::http::Uri; use axum_login::url_with_redirect_query; // Append ?next=%2Fdashboard to the login URL. let redirect_uri = "/dashboard".parse::().unwrap(); let url = url_with_redirect_query("/login", "next", redirect_uri).unwrap(); assert_eq!(url.to_string(), "/login?next=%2Fdashboard"); // Preserve an existing explicit redirect (do not overwrite). let redirect_uri = "/dashboard".parse::().unwrap(); let url = url_with_redirect_query("/login?next=%2Fkeep", "next", redirect_uri).unwrap(); assert_eq!(url.to_string(), "/login?next=%2Fkeep"); // Merge with other existing query params. let redirect_uri = "/page".parse::().unwrap(); let url = url_with_redirect_query("/login?lang=en", "next", redirect_uri).unwrap(); assert_eq!(url.to_string(), "/login?next=%2Fpage&lang=en"); ``` ``` -------------------------------- ### AuthzBackend Trait Implementation Source: https://context7.com/maxcountryman/axum-login/llms.txt Demonstrates how to implement the `AuthzBackend` trait to add user and group permission support. This includes fetching user-specific and group-specific permissions from a database. ```APIDOC ## `AuthzBackend` Trait Optional extension of `AuthnBackend` that adds user and group permission support. Override `get_user_permissions` and/or `get_group_permissions`; `get_all_permissions` and `has_perm` are provided with default implementations that merge both sets. ```rust use std::collections::HashSet; use axum_login::AuthzBackend; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Permission { pub name: String, } impl From<&str> for Permission { fn from(s: &str) -> Self { Permission { name: s.to_string() } } } impl AuthzBackend for Backend { type Permission = Permission; async fn get_user_permissions( &self, user: &Self::User, ) -> Result, Self::Error> { let rows: Vec<(String,)> = sqlx::query_as("SELECT permission FROM user_permissions WHERE user_id = ?") .bind(user.id()) .fetch_all(&self.db) .await?; Ok(rows.into_iter().map(|(name,)| Permission { name }).collect()) } async fn get_group_permissions( &self, user: &Self::User, ) -> Result, Self::Error> { let rows: Vec<(String,)> = sqlx::query_as( "SELECT gp.permission FROM group_permissions gp JOIN user_groups ug ON ug.group_id = gp.group_id WHERE ug.user_id = ?", ) .bind(user.id()) .fetch_all(&self.db) .await?; Ok(rows.into_iter().map(|(name,)| Permission { name }).collect()) } } // Usage: check a single permission async fn check(backend: &Backend, user: &User) { let allowed = backend.has_perm(user, Permission::from("admin.read")).await.unwrap(); // get_all_permissions merges user + group permissions let all = backend.get_all_permissions(user).await.unwrap(); println!("admin.read: {allowed}, all perms: {all:?}"); } ``` ``` -------------------------------- ### Require Builder - Basic Usage Source: https://context7.com/maxcountryman/axum-login/llms.txt Demonstrates the basic configuration of the Require builder for redirecting unauthenticated users and returning a 403 Forbidden status for unauthorized access. ```APIDOC ## `Require` Builder — Fine-Grained Middleware The `Require` builder is the primary long-term API surface. It supports custom decision predicates, configurable unauthenticated/unauthorized response handlers, and shared application state. ```rust use std::sync::Arc; use axum::{http::StatusCode, response::IntoResponse, routing::get, Router}; use axum_login::{ require::{Decision, PermissionMatch, PermissionsPredicate, RedirectHandler, Require, SimpleResponseHandler}, AuthSession, }; // ── Basic: redirect unauthenticated, 403 for unauthorized ────────────────── let require = Require::::builder() .unauthenticated(RedirectHandler::new().login_url("/login")) .unauthorized(SimpleResponseHandler::text(StatusCode::FORBIDDEN, "Access denied")) .build(); // ── Permission check with Any match mode ─────────────────────────────────── let require_perm = Require::::builder() .decision( PermissionsPredicate::::new() .with_permissions(["editor.write", "admin.write"]) .with_mode(PermissionMatch::Any), // allow if user has ANY of these ) .unauthenticated(RedirectHandler::new().login_url("/login")) .build(); // ── Custom async closure predicate with shared state ────────────────────── #[derive(Clone)] struct AppState { feature_enabled: bool } let state = AppState { feature_enabled: true }; let require_custom = Require::::builder_with_state(state) .decision(|auth_session: AuthSession, state: Arc| async move { let Some(_user) = auth_session.user().await else { return Decision::Unauthenticated; }; if state.feature_enabled { Decision::Allow } else { Decision::Unauthorized } }) .unauthenticated(RedirectHandler::new().login_url("/login")) .build(); // ── Wire it up ────────────────────────────────────────────────────────────── let app = Router::new() .route("/protected", get(protected_handler)) .route_layer(require_custom) .route("/login", get(login_page)) .layer(auth_layer); ``` ``` -------------------------------- ### Require Builder with Permission Check (Any Match) Source: https://context7.com/maxcountryman/axum-login/llms.txt Sets up `Require` middleware to allow access if the user possesses any of the specified permissions ('editor.write' or 'admin.write'). Redirects unauthenticated users. ```rust let require_perm = Require::::builder() .decision( PermissionsPredicate::::new() .with_permissions(["editor.write", "admin.write"]) .with_mode(PermissionMatch::Any), // allow if user has ANY of these ) .unauthenticated(RedirectHandler::new().login_url("/login")) .build(); ``` -------------------------------- ### Configure Axum Auth Service Layer Source: https://context7.com/maxcountryman/axum-login/llms.txt Combine your backend and a session layer into an Axum `Layer` using `AuthManagerLayerBuilder`. Add this layer last on the router to ensure it wraps all routes. Optionally customize the session data key. ```rust use axum::{routing::{get, post}, Router}; use axum_login::{ login_required, tower_sessions::{MemoryStore, SessionManagerLayer}, AuthManagerLayerBuilder, }; use tokio::net::TcpListener; #[tokio::main] async fn main() -> Result<(), Box> { // 1. Session store (use a persistent store like SqliteStore in production). let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store); // 2. Instantiate your backend (wraps your DB pool, etc.). let backend = Backend::new(db_pool); // 3. Build the auth layer; optionally customise the session data key. let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer) .with_data_key("myapp.auth") // default: "axum-login.data" .build(); // 4. Wire routes — protected routes go above the auth layer. let app = Router::new() .route("/dashboard", get(dashboard_handler)) .route_layer(login_required!(Backend, login_url = "/login")) .route("/login", get(login_page)) .route("/login", post(login_submit)) .route("/logout", get(logout_handler)) .layer(auth_layer); // <-- must be the outermost layer let listener = TcpListener::bind("0.0.0.0:3000").await?; axum::serve(listener, app.into_make_service()).await?; Ok(()) } ``` -------------------------------- ### PermissionsPredicate - Match Modes Source: https://context7.com/maxcountryman/axum-login/llms.txt Illustrates how to use the PermissionsPredicate to define authorization rules based on user permissions, supporting 'All', 'Any', and 'Exact' match modes. ```APIDOC ## `PermissionsPredicate` — Built-in Permission Predicate A ready-made `DecisionPredicate` that checks user and group permissions from an `AuthzBackend` against a required set, with configurable matching semantics (`All`, `Any`, or `Exact`). ```rust use axum_login::require::{PermissionMatch, PermissionsPredicate, Require}; // ALL of ["reports.read", "reports.export"] must be held. let all_required = PermissionsPredicate::::new() .with_permissions(["reports.read", "reports.export"]) .with_mode(PermissionMatch::All); // default // ANY one of these permissions is sufficient. let any_required = PermissionsPredicate::::new() .with_permissions(["admin.read", "superuser"]) .with_mode(PermissionMatch::Any); // User must hold EXACTLY this set (no more, no less). let exact = PermissionsPredicate::::new() .with_permissions(["viewer"]) .with_mode(PermissionMatch::Exact); let layer = Require::::builder() .decision(any_required) .unauthenticated(axum_login::require::RedirectHandler::new().login_url("/login")) .build(); ``` ``` -------------------------------- ### Add axum-login to Cargo.toml Source: https://github.com/maxcountryman/axum-login/blob/main/README.md Add this dependency to your Cargo.toml file to include axum-login in your project. ```toml [dependencies] axum-login = "0.18.0" ``` -------------------------------- ### Implement AuthnBackend Trait for Authentication Source: https://context7.com/maxcountryman/axum-login/llms.txt Describes an authentication backend. Implement `authenticate` to verify credentials and `get_user` to load users by ID. Password verification should be run on a dedicated thread as it is blocking. ```rust use axum_login::{AuthnBackend, UserId}; use password_auth::verify_password; use sqlx::SqlitePool; use tokio::task; #[derive(Debug, Clone, serde::Deserialize)] pub struct Credentials { pub username: String, pub password: String, pub next: Option, // optional post-login redirect } #[derive(Debug, Clone)] pub struct Backend { db: SqlitePool, } impl Backend { pub fn new(db: SqlitePool) -> Self { Self { db } } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] Sqlx(#[from] sqlx::Error), #[error(transparent)] TaskJoin(#[from] task::JoinError), } impl AuthnBackend for Backend { type User = User; type Credentials = Credentials; type Error = Error; async fn authenticate( &self, creds: Self::Credentials, ) -> Result, Self::Error> { let user: Option = sqlx::query_as("SELECT * FROM users WHERE username = ?") .bind(creds.username) .fetch_optional(&self.db) .await?; // Password verification is blocking; run it on a dedicated thread. task::spawn_blocking(|| { Ok(user.filter(|u| verify_password(creds.password, &u.password).is_ok())) }) .await? } async fn get_user(&self, user_id: &UserId) -> Result, Self::Error> { let user = sqlx::query_as("SELECT * FROM users WHERE id = ?") .bind(user_id) .fetch_optional(&self.db) .await?; Ok(user) } } // Convenience type alias used throughout your app. pub type AuthSession = axum_login::AuthSession; ``` -------------------------------- ### Require Builder with Custom Async Predicate and State Source: https://context7.com/maxcountryman/axum-login/llms.txt Demonstrates using a custom asynchronous closure for the decision predicate, incorporating shared application state (`AppState`) to determine access. Unauthenticated users are redirected. ```rust #[derive(Clone)] struct AppState { feature_enabled: bool } let state = AppState { feature_enabled: true }; let require_custom = Require::::builder_with_state(state) .decision(|auth_session: AuthSession, state: Arc| async move { let Some(_user) = auth_session.user().await else { return Decision::Unauthenticated; }; if state.feature_enabled { Decision::Allow } else { Decision::Unauthorized } }) .unauthenticated(RedirectHandler::new().login_url("/login")) .build(); ``` -------------------------------- ### Axum-Login Feature Flag Configuration Source: https://github.com/maxcountryman/axum-login/blob/main/README.md To use only the builder-based require module without macros, configure your Cargo.toml with default-features = false and enable the 'require-builder' feature. ```toml [dependencies] axum-login = { version = "0.18.0", default-features = false, features = ["require-builder"] } ``` -------------------------------- ### Merging Redirect with Other Query Parameters Source: https://context7.com/maxcountryman/axum-login/llms.txt Shows how `url_with_redirect_query` merges the new `next` redirect parameter with other existing query parameters in the login URL. ```rust // Merge with other existing query params. let redirect_uri = "/page".parse::().unwrap(); let url = url_with_redirect_query("/login?lang=en", "next", redirect_uri).unwrap(); assert_eq!(url.to_string(), "/login?next=%2Fpage&lang=en"); ``` -------------------------------- ### Building Require Layer with PermissionsPredicate Source: https://context7.com/maxcountryman/axum-login/llms.txt Combines a `PermissionsPredicate` (using 'Any' match mode) with the `Require` builder to create a middleware layer that redirects unauthenticated users. ```rust let layer = Require::::builder() .decision(any_required) .unauthenticated(axum_login::require::RedirectHandler::new().login_url("/login")) .build(); ``` -------------------------------- ### PermissionsPredicate with All Match Mode Source: https://context7.com/maxcountryman/axum-login/llms.txt Creates a `PermissionsPredicate` that requires the user to possess all specified permissions ('reports.read' and 'reports.export'). This is the default mode. ```rust use axum_login::require::{PermissionMatch, PermissionsPredicate, Require}; // ALL of ["reports.read", "reports.export"] must be held. let all_required = PermissionsPredicate::::new() .with_permissions(["reports.read", "reports.export"]) .with_mode(PermissionMatch::All); // default ``` -------------------------------- ### Wiring Require Middleware into Axum Router Source: https://context7.com/maxcountryman/axum-login/llms.txt Shows how to apply the custom `Require` middleware (`require_custom`) to a specific route ('/protected') within an Axum application. ```rust let app = Router::new() .route("/protected", get(protected_handler)) .route_layer(require_custom) .route("/login", get(login_page)) .layer(auth_layer); ``` -------------------------------- ### PermissionsPredicate with Any Match Mode Source: https://context7.com/maxcountryman/axum-login/llms.txt Constructs a `PermissionsPredicate` that allows access if the user holds at least one of the listed permissions ('admin.read' or 'superuser'). ```rust // ANY one of these permissions is sufficient. let any_required = PermissionsPredicate::::new() .with_permissions(["admin.read", "superuser"]) .with_mode(PermissionMatch::Any); ``` -------------------------------- ### Axum Route Protection with permission_required! Macro Source: https://context7.com/maxcountryman/axum-login/llms.txt Extends login_required! to enforce specific permissions. Unauthenticated users are handled as per login_required!, while authenticated users lacking permissions receive 403 Forbidden. Supports multiple permissions and custom redirect fields. ```rust use axum::{routing::get, Router}; use axum_login::permission_required; // Require a single permission; 401 for unauthenticated, 403 for unauthorized. let admin_routes = Router::new() .route("/admin", get(admin_handler)) .route_layer(permission_required!(Backend, "admin.read")); // Multiple permissions (ALL must be held) + login redirect. let editor_routes = Router::new() .route("/posts/edit", get(edit_handler)) .route_layer(permission_required!( Backend, login_url = "/login", "posts.read", "posts.write" )); // Custom redirect field. let reports = Router::new() .route("/reports", get(reports_handler)) .route_layer(permission_required!( Backend, login_url = "/signin", redirect_field = "next_uri", "reports.view" )); ``` -------------------------------- ### Axum Handlers with AuthSession Source: https://context7.com/maxcountryman/axum-login/llms.txt Demonstrates how to use the AuthSession extractor in Axum handlers for login, accessing the current user, and logging out. Requires a Backend type alias and Credentials struct. ```rust use axum::response::IntoResponse; use axum_login::AuthSession; // Type alias defined alongside your backend implementation. type AuthSession = axum_login::AuthSession; // Login handler: validate credentials, create session. async fn login( mut auth_session: AuthSession, Form(creds): Form, ) -> impl IntoResponse { // authenticate() delegates to AuthnBackend::authenticate. let user = match auth_session.authenticate(creds.clone()).await { Ok(Some(user)) => user, Ok(None) => return StatusCode::UNAUTHORIZED.into_response(), Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(), }; // login() stores the user ID + auth hash in the session, cycling the // session ID to mitigate session-fixation attacks. if auth_session.login(&user).await.is_err() { return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } let redirect = creds.next.as_deref().unwrap_or("/"); Redirect::to(redirect).into_response() } // Protected handler: access the already-hydrated user. async fn dashboard(auth_session: AuthSession) -> impl IntoResponse { match auth_session.user().await { Some(user) => format!("Hello, {}!", user.username).into_response(), None => StatusCode::UNAUTHORIZED.into_response(), } } // Logout handler: flushes the session. async fn logout(auth_session: AuthSession) -> impl IntoResponse { match auth_session.logout().await { Ok(_) => Redirect::to("/login").into_response(), Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), } } ``` -------------------------------- ### Axum Route Protection with login_required! Macro Source: https://context7.com/maxcountryman/axum-login/llms.txt Applies middleware to protect routes, returning 401 Unauthorized for unauthenticated requests or redirecting to a specified login URL. Supports custom redirect field names. ```rust use axum::{routing::get, Router}; use axum_login::login_required; // Variant 1: return 401 for unauthenticated requests. let protected = Router::new() .route("/profile", get(profile_handler)) .route_layer(login_required!(Backend)); // Variant 2: redirect to /login with a `next` query param appended. // Unauthenticated GET /dashboard → 307 /login?next=%2Fdashboard let protected = Router::new() .route("/dashboard", get(dashboard_handler)) .route_layer(login_required!(Backend, login_url = "/login")); // Variant 3: custom redirect field name. // → 307 /signin?redirect_to=%2Fdashboard let protected = Router::new() .route("/dashboard", get(dashboard_handler)) .route_layer(login_required!( Backend, login_url = "/signin", redirect_field = "redirect_to" )); ``` -------------------------------- ### Preserving Existing Redirect in Login URL Source: https://context7.com/maxcountryman/axum-login/llms.txt Demonstrates that `url_with_redirect_query` will not overwrite an existing `next` query parameter if one is already present in the login URL. ```rust // Preserve an existing explicit redirect (do not overwrite). let redirect_uri = "/dashboard".parse::().unwrap(); let url = url_with_redirect_query("/login?next=%2Fkeep", "next", redirect_uri).unwrap(); assert_eq!(url.to_string(), "/login?next=%2Fkeep"); ``` -------------------------------- ### PermissionsPredicate with Exact Match Mode Source: https://context7.com/maxcountryman/axum-login/llms.txt Defines a `PermissionsPredicate` that requires the user to have exactly the specified permission ('viewer'), no more and no less. ```rust // User must hold EXACTLY this set (no more, no less). let exact = PermissionsPredicate::::new() .with_permissions(["viewer"]) .with_mode(PermissionMatch::Exact); ``` -------------------------------- ### Appending Redirect Query Parameter to Login URL Source: https://context7.com/maxcountryman/axum-login/llms.txt Appends a URL-encoded `next` query parameter to a login URL, preserving existing query parameters and respecting an explicit redirect already present. ```rust use axum::http::Uri; use axum_login::url_with_redirect_query; // Append ?next=%2Fdashboard to the login URL. let redirect_uri = "/dashboard".parse::().unwrap(); let url = url_with_redirect_query("/login", "next", redirect_uri).unwrap(); assert_eq!(url.to_string(), "/login?next=%2Fdashboard"); ``` -------------------------------- ### permission_required! Macro Source: https://context7.com/maxcountryman/axum-login/llms.txt Extends the `login_required!` macro by adding permission checks. Unauthenticated users are handled similarly to `login_required!`, while authenticated users without the necessary permissions receive a 403 Forbidden status. ```APIDOC ## `permission_required!` Macro Extends `login_required!` with permission checks. Unauthenticated users are redirected (or receive 401); authenticated users lacking the required permissions receive `403 Forbidden`. ```rust use axum::{routing::get, Router}; use axum_login::permission_required; // Require a single permission; 401 for unauthenticated, 403 for unauthorized. let admin_routes = Router::new() .route("/admin", get(admin_handler)) .route_layer(permission_required!(Backend, "admin.read")); // Multiple permissions (ALL must be held) + login redirect. let editor_routes = Router::new() .route("/posts/edit", get(edit_handler)) .route_layer(permission_required!( Backend, login_url = "/login", "posts.read", "posts.write" )); // Custom redirect field. let reports = Router::new() .route("/reports", get(reports_handler)) .route_layer(permission_required!( Backend, login_url = "/signin", redirect_field = "next_uri", "reports.view" )); ``` ``` -------------------------------- ### AuthSession Extractor and Workflow Source: https://context7.com/maxcountryman/axum-login/llms.txt AuthSession provides an Axum extractor and the primary API for managing user authentication states, including login, logout, and accessing the current user within handlers. ```APIDOC ## `AuthSession` — Extractor and Auth Workflow `AuthSession` is both an Axum extractor and the primary API surface for login, logout, and accessing the current user inside handlers. ```rust use axum::{ http::StatusCode, response::{IntoResponse, Redirect}, Form, }; // Type alias defined alongside your backend implementation. type AuthSession = axum_login::AuthSession; // Login handler: validate credentials, create session. async fn login( mut auth_session: AuthSession, Form(creds): Form, ) -> impl IntoResponse { // authenticate() delegates to AuthnBackend::authenticate. let user = match auth_session.authenticate(creds.clone()).await { Ok(Some(user)) => user, Ok(None) => return StatusCode::UNAUTHORIZED.into_response(), Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(), }; // login() stores the user ID + auth hash in the session, cycling the // session ID to mitigate session-fixation attacks. if auth_session.login(&user).await.is_err() { return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } let redirect = creds.next.as_deref().unwrap_or("/"); Redirect::to(redirect).into_response() } // Protected handler: access the already-hydrated user. async fn dashboard(auth_session: AuthSession) -> impl IntoResponse { match auth_session.user().await { Some(user) => format!("Hello, {}!", user.username).into_response(), None => StatusCode::UNAUTHORIZED.into_response(), } } // Logout handler: flushes the session. async fn logout(auth_session: AuthSession) -> impl IntoResponse { match auth_session.logout().await { Ok(_) => Redirect::to("/login").into_response(), Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), } } ``` ``` -------------------------------- ### login_required! Macro Source: https://context7.com/maxcountryman/axum-login/llms.txt A convenience macro that creates a tower middleware layer to enforce user authentication. Unauthenticated requests are either met with a 401 Unauthorized status or redirected to a specified login URL. ```APIDOC ## `login_required!` Macro Convenience macro that produces a `tower` middleware layer requiring an authenticated user. Unauthenticated requests receive `401 Unauthorized` (no arguments) or a `307 Temporary Redirect` to the specified login URL. ```rust use axum::{routing::get, Router}; use axum_login::login_required; // Variant 1: return 401 for unauthenticated requests. let protected = Router::new() .route("/profile", get(profile_handler)) .route_layer(login_required!(Backend)); // Variant 2: redirect to /login with a `next` query param appended. // Unauthenticated GET /dashboard → 307 /login?next=%2Fdashboard let protected = Router::new() .route("/dashboard", get(dashboard_handler)) .route_layer(login_required!(Backend, login_url = "/login")); // Variant 3: custom redirect field name. // → 307 /signin?redirect_to=%2Fdashboard let protected = Router::new() .route("/dashboard", get(dashboard_handler)) .route_layer(login_required!( Backend, login_url = "/signin", redirect_field = "redirect_to" )); ``` ``` -------------------------------- ### Login Form Template Source: https://github.com/maxcountryman/axum-login/blob/main/examples/sqlite/templates/login.html This Jinja template renders a login form. It iterates over messages to display them and includes input fields for username and password. It also has a conditional block for 'next' parameter. ```html Login label { display: block; margin-bottom: 5px; } {% for message in messages %}* **{{ message }}** {% endfor %} User login Username Password {% if let Some(next) = next %} {% endif %} ``` -------------------------------- ### Implement AuthUser Trait for User Struct Source: https://context7.com/maxcountryman/axum-login/llms.txt Defines the minimal interface for user types, including ID and session authentication hash. Ensure the ID is serializable and unique. The session_auth_hash is used to validate sessions on each request. ```rust use axum_login::AuthUser; use serde::{Deserialize, Serialize}; use sqlx::FromRow; #[derive(Clone, FromRow, Serialize, Deserialize)] pub struct User { id: i64, pub username: String, password: String, // argon2 hash stored in DB } // Implement Debug manually to avoid logging the password hash. impl std::fmt::Debug for User { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("User") .field("id", &self.id) .field("username", &self.username) .field("password", &"[redacted]") .finish() } } impl AuthUser for User { type Id = i64; fn id(&self) -> Self::Id { self.id } // Changing the password hash invalidates the session automatically. fn session_auth_hash(&self) -> &[u8] { self.password.as_bytes() } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.