### Axum Full-Stack App with Session and Auth Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt This is the main application setup. It initializes the database pool, configures session and authentication layers, defines routes, and starts the Axum server. Ensure you have the necessary dependencies like `axum`, `axum-session`, `axum-session-auth`, `axum-session-sqlx`, `sqlx`, and `tokio`. ```rust use async_trait::async_trait; use axum::{http::Method, routing::get, Router}; use axum_session::{SessionConfig, SessionLayer, SessionStore}; use axum_session_auth::* use axum_session_sqlx::SessionSqlitePool; use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; use std::{collections::HashSet, str::FromStr}; use tokio::net::TcpListener; #[derive(Debug, Clone)] pub struct User { pub id: i32, pub anonymous: bool, pub username: String, pub permissions: HashSet, } impl Default for User { fn default() -> Self { User { id: 1, anonymous: true, username: "Guest".into(), permissions: HashSet::new() } } } #[async_trait] impl Authentication for User { async fn load_user(userid: i64, pool: Option<&SqlitePool>) -> Result { // Simplified: return hard-coded users if userid == 2 { Ok(User { id: 2, anonymous: false, username: "Alice".into(), permissions: ["Category::View".into()].into() }) } else { Ok(User::default()) } } fn is_authenticated(&self) -> bool { !self.anonymous } fn is_active(&self) -> bool { !self.anonymous } fn is_anonymous(&self) -> bool { self.anonymous } } #[async_trait] impl HasPermission for User { async fn has(&self, perm: &str, _pool: &Option<&SqlitePool>) -> bool { self.permissions.contains(perm) } } #[tokio::main] async fn main() { let pool = SqlitePoolOptions::new() .connect_with(SqliteConnectOptions::from_str("sqlite::memory:").unwrap()) .await.unwrap(); let session_config = SessionConfig::default().with_table_name("sessions"); let auth_config = AuthConfig::::default().with_anonymous_user_id(Some(1)); let session_store = SessionStore::::new(Some(pool.clone().into()), session_config) .await.unwrap(); let app = Router::new() .route("/", get(greet)) .route("/login", get(login)) .route("/logout", get(logout)) .route("/perm", get(perm_check)) .layer( AuthSessionLayer::::new(Some(pool)) .with_config(auth_config), ) .layer(SessionLayer::new(session_store)); axum::serve(TcpListener::bind("0.0.0.0:3000").await.unwrap(), app) .await.unwrap(); } async fn greet(auth: AuthSession) -> String { let user = auth.current_user.clone().unwrap_or_default(); let views: usize = auth.session.get("views").unwrap_or(0); auth.session.set("views", views + 1); format!("Hello {}! You have visited {} time(s).", user.username, views + 1) } async fn login(auth: AuthSession) -> &'static str { auth.login_user(2_i64); auth.remember_user(true); "Logged in as Alice. Visit /greet." } async fn logout(auth: AuthSession) -> &'static str { auth.logout_user(); "Logged out." } async fn perm_check( method: Method, auth: AuthSession, ) -> String { let user = auth.current_user.clone().unwrap_or_default(); if !Auth::::build([Method::GET], false) .requires(Rights::any([ Rights::permission("Category::View"), Rights::permission("Admin::View"), ])) .validate(&user, &method, None) .await { return format!("'{}' lacks required permissions.", user.username); } format!("'{}' has access. Permissions: {:?}", user.username, user.permissions) } ``` -------------------------------- ### AuthSessionLayer Setup Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Illustrates how to set up the AuthSessionLayer middleware in an Axum router, ensuring it's placed correctly relative to SessionLayer. ```APIDOC ## `AuthSessionLayer` — Tower Middleware Layer `AuthSessionLayer` is the Tower `Layer` that wires everything together. It must be added to the Axum router **before** (i.e., wrapping) `SessionLayer`. The generic parameters are ``. ```rust use axum::{Router, routing::get}; use axum_session::{SessionConfig, SessionLayer, SessionStore}; use axum_session_auth::{AuthConfig, AuthSessionLayer}; use axum_session_sqlx::SessionSqlitePool; use sqlx::SqlitePool; use tokio::net::TcpListener; #[tokio::main] async fn main() { let pool = SqlitePool::connect("sqlite::memory:").await.unwrap(); let session_config = SessionConfig::default().with_table_name("sessions"); let auth_config = AuthConfig::::default().with_anonymous_user_id(Some(1)); let session_store = SessionStore::::new(Some(pool.clone().into()), session_config) .await .unwrap(); let app = Router::new() .route("/", get(greet)) // AuthSessionLayer wraps inner routes — added first (outermost after SessionLayer) .layer( AuthSessionLayer::::new(Some(pool)) .with_config(auth_config), ) // SessionLayer must be the outermost layer (executed first by Tower) .layer(SessionLayer::new(session_store)); let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } async fn greet(auth: AuthSession) -> String { format!("Hello, {}!", auth.current_user.unwrap_or_default().username) } ``` ``` -------------------------------- ### Axum Application Setup with Session and Auth Layers Source: https://github.com/ascendingcreations/axumsessionauth/blob/main/README.md Sets up an Axum router with session and authentication layers. The AuthSessionLayer should be placed inside the SessionLayer due to Axum's onion-like layer processing. ```rust use sqlx::{PgPool, ConnectOptions, postgres::{PgPoolOptions, PgConnectOptions}}; use std::net::SocketAddr; use axum_session::{Session, SessionConfig, SessionLayer, DatabasePool}; use axum_session_auth::{AuthSession, AuthSessionLayer, Authentication, AuthConfig, HasPermission}; use axum_session_sqlx::SessionPgPool; use axum::{ Router, routing::get, }; #[tokio::main] async fn main() { # async { let pool = connect_to_database().await.unwrap(); let session_config = SessionConfig::default() .with_database("test") .with_table_name("test_table"); let auth_config = AuthConfig::::default().with_anonymous_user_id(Some(1)); let session_store = SessionStore::::new(Some(pool.clone().into()), session_config); // Build our application with some routes let app = Router::new() .route("/greet/:name", get(greet)) .layer(AuthSessionLayer::::new(Some(pool)).with_config(auth_config)) //Axum Layer must come after due to how Axum loads each part like an Onion. Outer layer first. Inner layer last. .layer(SessionLayer::new(session_store)); // Run it let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); # }; } // We can obtain the method to compare with the methods we allow, which is useful if this supports multiple methods. // When called, auth is loaded in the background for you. async fn greet(method: Method, auth: AuthSession) -> &'static str { let mut count: usize = auth.session.get("count").unwrap_or(0); // We will get the user if not, then a guest which should be our default. let current_user = auth.current_user.clone().unwrap_or_default(); count += 1; // Session is also included with Auth so there's no need to require it in the function arguments if you're using AuthSession. auth.session.set("count", count); // If, for some reason, you needed to update your user's permissions // or data that is cached, then you will want to clear the user cache if it is enabled. // The user Cache is enabled by default. To clear, simply use: auth.cache_clear_user(1).await; // To clear all cached user data for a large update: auth.cache_clear_all().await; // This is our Auth Permission Builder and Rights Checker. We build it with methods to check for permissions. // In this case, if the page is loaded using Method::Get, it will proceed successfully. However, if Method::Post is used, it will fail with the "no Permissions!" error. // The boolean value "false" is used to determine whether authentication is required. When set to true, it triggers the function is_authenticated(). if !Auth::::build([Method::Get], false) // We prepare which rights we accept or deny from guests or other users. .requires(Rights::none([ Rights::permission("Token::UseAdmin"), Rights::permission("Token::ModifyPerms"), ])) // We then validate the current user and method. We also pass our database along for database permissions checking if required; otherwise, None. .validate(¤t_user, &method, None) .await { // We return a "No Permissions" message if validation fails for any reason. return format!("No Permissions! for {}", current_user.username)[]; } // Since we had the is_authenticated set to false Above we will instead use it to log in our Guest user. if !auth.is_authenticated() { // Set the user ID of the User to the Session so it can be Auto Loaded the next load or redirect auth.login_user(2); // Set the session to be long term. Good for Remember me type instances. auth.remember_user(true); // We don't currently know the username until the next page access. // so Normally we would Redirect here after login if we did indeed log in. // But in this case we will just let the user know to reload the page for the example. "You have Logged in! Please Refresh the page to display the username and counter." } else { // Upon page reload, if the user possesses all necessary permissions, the method is accurate, and they are logged in, // their username and a count that increments with each page refresh will be displayed. format!("{}-{}", current_user.username, count)[..] }; } #[derive(Clone, Debug)] pub struct User { pub id: i32, pub anonymous: bool, pub username: String, } // This is only used if you want to use Token based Authentication checks #[async_trait] impl HasPermission for User { ``` -------------------------------- ### Add Axum Session Auth Dependency Source: https://github.com/ascendingcreations/axumsessionauth/blob/main/README.md Add the axum_session_auth crate to your Cargo.toml file. This example shows the configuration for Postgres with rustls. ```toml # Cargo.toml [dependencies] # Postgres + rustls axum_session_auth = { version = "0.20.0" } axum_session_sqlx = { version = "0.9.0" } ``` -------------------------------- ### Invalidate User Cache with `AuthSession::cache_clear_user` / `cache_clear_all` Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Call `cache_clear_user` to invalidate a specific user's cache after data updates. Use `cache_clear_all` to invalidate all user caches, for example, after bulk permission changes. ```rust async fn update_username_handler( auth: AuthSession, // Imagine axum extractors provide new_name and target_id here ) -> &'static str { let target_id: i64 = 42; // ... perform DB update ... // sqlx::query("UPDATE users SET username = $1 WHERE id = $2") // .bind(&new_name).bind(target_id).execute(&pool).await.unwrap(); // Evict just the affected user from the shared cache auth.cache_clear_user(target_id); // Or evict every cached user (e.g., after a bulk permission change) // auth.cache_clear_all(); "User updated. Cache invalidated." } ``` -------------------------------- ### Connect to PostgreSQL Database in Rust Source: https://github.com/ascendingcreations/axumsessionauth/blob/main/README.md This function demonstrates how to establish a connection to a PostgreSQL database using SQLx. It returns a `Result` containing the database pool or an error. This is a foundational step for any database-dependent operation. ```rust async fn connect_to_database() -> anyhow::Result> { // ... # unimplemented!() } ``` -------------------------------- ### AuthConfig Configuration Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Demonstrates how to configure the AuthConfig for anonymous user fallback, session cache lifetime, session key name, and caching behavior. ```APIDOC ## `AuthConfig` `AuthConfig` configures the behaviour of the auth layer: anonymous user fallback, user cache lifetime, session key name, and whether caching is enabled at all. ```rust use axum_session_auth::AuthConfig; use chrono::Duration; // Full configuration example let auth_config = AuthConfig::::default() // Automatically log unauthenticated visitors in as user ID 1 (a "Guest" account) .with_anonymous_user_id(Some(1)) // Keep cached user data for 12 hours of inactivity .with_max_age(Duration::hours(12)) // Custom key used inside the session store for the user ID .with_session_id("my_app_user_id") // Enable the in-memory user cache (default: true) .set_cache(true); // Minimal: just set an anonymous user let minimal_config = AuthConfig::::default().with_anonymous_user_id(Some(1)); ``` ``` -------------------------------- ### Implement NullPool for In-Memory Authentication Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Use `Arc>` as the pool type for managing users entirely in memory when no backing database is available. This requires implementing `Authentication` and `HasPermission` traits for your user type. ```rust use std::sync::Arc; use async_trait::async_trait; use axum_session_auth::{Authentication, HasPermission, AuthConfig, AuthSessionLayer, AuthSession}; use axum_session_sqlx::SessionSqlitePool; // still needed for session storage use sqlx::SqlitePool; type NullPool = Arc>; #[async_trait] impl Authentication for User { async fn load_user(userid: i64, _pool: Option<&NullPool>) -> Result { // All user data is hard-coded / managed in memory match userid { 1 => Ok(User::default()), // Guest 2 => Ok(User { id: 2, anonymous: false, username: "Test".into(), permissions: ["Category::View".into()].into() }), _ => Err(anyhow::anyhow!("Unknown user")), } } fn is_authenticated(&self) -> bool { !self.anonymous } fn is_active(&self) -> bool { !self.anonymous } fn is_anonymous(&self) -> bool { self.anonymous } } #[async_trait] impl HasPermission for User { async fn has(&self, perm: &str, _pool: &Option<&NullPool>) -> bool { self.permissions.contains(perm) } } ``` ```rust // In main(): // let nullpool: NullPool = Arc::new(None); // AuthSessionLayer::::new(Some(nullpool)) // .with_config(auth_config) ``` -------------------------------- ### Log In a User with AuthSession::login_user Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Use `login_user` to store the user's ID in the session and renew the session ID to prevent fixation attacks. Optionally, use `remember_user` to persist the session. ```rust async fn login_handler( auth: AuthSession, ) -> &'static str { // Typically you would verify credentials here before calling login_user. // user_id 2 corresponds to a non-anonymous account in the DB. auth.login_user(2_i64); // Optionally persist the session beyond the browser session ("remember me") auth.remember_user(true); "Login successful. Refresh to see your profile." } ``` -------------------------------- ### Gate Handlers with `Auth::build` / `Auth::requires` / `Auth::validate` Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt The `Auth` guard checks HTTP method, authentication status, and `Rights`. Use `Auth::build` to set allowed methods and authentication requirements, `requires` for permission checks, and `validate` to perform the check. ```rust use axum::http::Method; use axum_session_auth::{Auth, Rights, AuthSession}; use axum_session_sqlx::SessionSqlitePool; use sqlx::SqlitePool; async fn admin_panel( method: Method, auth: AuthSession, ) -> String { let user = auth.current_user.clone().unwrap_or_default(); // Build accepts allowed HTTP methods and whether authentication is mandatory let allowed = Auth::::build([Method::GET], /* auth_required */ true) .requires(Rights::all([ Rights::permission("admin:login"), Rights::permission("admin:view_dashboard"), ])) // Pass None for the DB pool when permissions are cached in-memory on the User struct .validate(&user, &method, None) .await; if !allowed { return format!("Access denied for user '{}'.", user.username); } format!("Welcome to the admin panel, {}!", user.username) } ``` ```rust // Example using an OR permission check without requiring authentication async fn public_content( method: Method, auth: AuthSession, ) -> String { let user = auth.current_user.clone().unwrap_or_default(); let allowed = Auth::::build([Method::GET], false) .requires(Rights::any([ Rights::permission("Category::View"), Rights::permission("Admin::View"), ])) .validate(&user, &method, None) .await; if !allowed { return "No permissions to view this content.".to_owned(); } format!("Content visible. Permissions: {:?}", user.permissions) } ``` -------------------------------- ### Build Permission Rules with `Rights` Enum Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Use the `Rights` enum to build composable permission expressions. Combine `Rights::all`, `Rights::any`, and `Rights::none` for AND, OR, and NOT logic respectively. Permissions can be nested for complex policies. ```rust use axum_session_auth::Rights; // Require ALL of these permissions (AND logic) let admin_rights = Rights::all([ Rights::permission("admin:login"), Rights::permission("admin:view_dashboard"), ]); // Require AT LEAST ONE of these permissions (OR logic) let viewer_rights = Rights::any([ Rights::permission("Category::View"), Rights::permission("Admin::View"), ]); // Deny if the user holds ANY of these permissions (NOT logic) let non_banned = Rights::none([ Rights::permission("status:banned"), Rights::permission("status:suspended"), ]); // Nested composition: must be non-banned AND have at least one view right let combined = Rights::all([ non_banned, viewer_rights, ]); ``` -------------------------------- ### Implement User Authentication Traits in Rust Source: https://github.com/ascendingcreations/axumsessionauth/blob/main/README.md This snippet shows the implementation of `Authentication` traits for a `User` struct, including methods for loading users, checking authentication status, and determining anonymity. It's useful for defining how users are managed within the authentication system. ```rust async fn has(&self, perm: &String, _pool: &Option<&PgPool>) -> bool { match &perm[..] { "Token::UseAdmin" => true, "Token::ModifyUser" => true, _ => false, } } } #[async_trait] impl Authentication for User { // This is run when the user has logged in and has not yet been Cached in the system. // Once ran it will load and cache the user. async fn load_user(userid: i64, _pool: Option<&PgPool>) -> Result { Ok(User { id: userid, anonymous: true, username: "Guest".to_string(), }) } // This function is used internally to determine if they are logged in or not. fn is_authenticated(&self) -> bool { !self.anonymous } fn is_active(&self) -> bool { !self.anonymous } fn is_anonymous(&self) -> bool { self.anonymous } } ``` -------------------------------- ### Add axum_session_auth to Cargo.toml Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Include axum_session_auth and related crates in your project's dependencies. Ensure compatibility with your chosen session store and database. ```toml [dependencies] axum_session_auth = { version = "0.20.0" } axum_session = { version = "0.20.0" } axum_session_sqlx = { version = "0.9.0" } # or axum_session_surreal, axum_session_mongo, etc. async-trait = "0.1" tokio = { version = "1", features = ["full"] } sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio"] } anyhow = "1" # Optional feature flags # axum_session_auth = { version = "0.20.0", features = ["advanced"] } # axum_session_auth = { version = "0.20.0", features = ["rest_mode"] } # axum_session_auth = { version = "0.20.0", features = ["key-store"] } ``` -------------------------------- ### Configure Auth Behavior with AuthConfig Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Use AuthConfig to customize anonymous user handling, cache lifetimes, session key names, and caching behavior. Set an anonymous user ID to automatically log in unauthenticated visitors. ```rust use axum_session_auth::AuthConfig; use chrono::Duration; // Full configuration example let auth_config = AuthConfig::::default() // Automatically log unauthenticated visitors in as user ID 1 (a "Guest" account) .with_anonymous_user_id(Some(1)) // Keep cached user data for 12 hours of inactivity .with_max_age(Duration::hours(12)) // Custom key used inside the session store for the user ID .with_session_id("my_app_user_id") // Enable the in-memory user cache (default: true) .set_cache(true); // Minimal: just set an anonymous user let minimal_config = AuthConfig::::default().with_anonymous_user_id(Some(1)); ``` -------------------------------- ### AuthSession::login_user Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Details on how to log in a user by storing their ID in the session and renewing the session ID to prevent fixation attacks. ```APIDOC ## `AuthSession::login_user` — Log In a User Stores the user's ID in the session and renews the session ID to prevent fixation attacks. ```rust async fn login_handler( auth: AuthSession, ) -> &'static str { // Typically you would verify credentials here before calling login_user. // user_id 2 corresponds to a non-anonymous account in the DB. auth.login_user(2_i64); // Optionally persist the session beyond the browser session ("remember me") auth.remember_user(true); "Login successful. Refresh to see your profile." } ``` ``` -------------------------------- ### Implement Authentication Trait for User Type Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Implement the `Authentication` trait on your user type to define how users are loaded from the database and to specify their authentication and activity status. This is crucial for automatic user loading on each request. ```rust use async_trait::async_trait; use axum_session_auth::Authentication; use sqlx::SqlitePool; use std::collections::HashSet; use anyhow::Error; #[derive(Debug, Clone)] pub struct User { pub id: i32, pub anonymous: bool, pub username: String, pub permissions: HashSet, } impl Default for User { fn default() -> Self { User { id: 1, anonymous: true, username: "Guest".into(), permissions: HashSet::new() } } } #[async_trait] impl Authentication for User { // Called once per user per cache-miss; result is cached automatically. async fn load_user(userid: i64, pool: Option<&SqlitePool>) -> Result { let pool = pool.ok_or_else(|| anyhow::anyhow!("no pool"))?; let row = sqlx::query_as::<_, (i32, bool, String)>( "SELECT id, anonymous, username FROM users WHERE id = $1" ) .bind(userid) .fetch_one(pool) .await?; Ok(User { id: row.0, anonymous: row.1, username: row.2, permissions: HashSet::new() }) } fn is_authenticated(&self) -> bool { !self.anonymous } fn is_active(&self) -> bool { !self.anonymous } fn is_anonymous(&self) -> bool { self.anonymous } } ``` -------------------------------- ### Auth::build / Auth::requires / Auth::validate — Gate a Handler Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt `Auth` is a guard struct that checks HTTP method, authentication status, and `Rights` in a single asynchronous call. It returns `true` if access should be granted. ```APIDOC ## `Auth::build` / `Auth::requires` / `Auth::validate` — Gate a Handler `Auth` is the guard struct that checks HTTP method, authentication status, and `Rights` in one async call. Returns `true` if access should be granted. ```rust use axum::http::Method; use axum_session_auth::{Auth, Rights, AuthSession}; use axum_session_sqlx::SessionSqlitePool; use sqlx::SqlitePool; async fn admin_panel( method: Method, auth: AuthSession, ) -> String { let user = auth.current_user.clone().unwrap_or_default(); // Build accepts allowed HTTP methods and whether authentication is mandatory let allowed = Auth::::build([Method::GET], /* auth_required */ true) .requires(Rights::all([ Rights::permission("admin:login"), Rights::permission("admin:view_dashboard"), ])) // Pass None for the DB pool when permissions are cached in-memory on the User struct .validate(&user, &method, None) .await; if !allowed { return format!("Access denied for user '{}'.", user.username); } format!("Welcome to the admin panel, {}!", user.username) } // Example using an OR permission check without requiring authentication async fn public_content( method: Method, auth: AuthSession, ) -> String { let user = auth.current_user.clone().unwrap_or_default(); let allowed = Auth::::build([Method::GET], false) .requires(Rights::any([ Rights::permission("Category::View"), Rights::permission("Admin::View"), ])) .validate(&user, &method, None) .await; if !allowed { return "No permissions to view this content.".to_owned(); } format!("Content visible. Permissions: {:?}", user.permissions) } ``` ``` -------------------------------- ### Integrate AuthSessionLayer Middleware Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Add AuthSessionLayer to your Axum router before SessionLayer to enable authentication. This layer requires generic parameters for UserType, IDType, SessionPoolType, and DatabasePoolType. ```rust use axum::{Router, routing::get}; use axum_session::{SessionConfig, SessionLayer, SessionStore}; use axum_session_auth::{AuthConfig, AuthSessionLayer}; use axum_session_sqlx::SessionSqlitePool; use sqlx::SqlitePool; use tokio::net::TcpListener; #[tokio::main] async fn main() { let pool = SqlitePool::connect("sqlite::memory:").await.unwrap(); let session_config = SessionConfig::default().with_table_name("sessions"); let auth_config = AuthConfig::::default().with_anonymous_user_id(Some(1)); let session_store = SessionStore::::new(Some(pool.clone().into()), session_config) .await .unwrap(); let app = Router::new() .route("/", get(greet)) // AuthSessionLayer wraps inner routes — added first (outermost after SessionLayer) .layer( AuthSessionLayer::::new(Some(pool)) .with_config(auth_config), ) // SessionLayer must be the outermost layer (executed first by Tower) .layer(SessionLayer::new(session_store)); let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } async fn greet(auth: AuthSession) -> String { format!("Hello, {}!", auth.current_user.unwrap_or_default().username) } ``` -------------------------------- ### Inspect Session State with AuthStatus Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Use `AuthStatus` to detect stale or switched sessions without a full reload when the `advanced` feature is enabled. This allows for granular handling of different session states. ```rust #[cfg(feature = "advanced")] use axum_session_auth::AuthStatus; #[cfg(feature = "advanced")] async fn check_status_handler( mut auth: AuthSession, ) -> &'static str { match auth.is_logged_in() { AuthStatus::LoggedIn => "Session valid and user is logged in.", AuthStatus::LoggedOut => "User is not logged in.", AuthStatus::DifferentID => "Session ID switched — possible account change.", AuthStatus::StaleUser => { // User was evicted from cache; reload and continue auth.reload_user().await; "User data was stale; reloaded from database." } } } ``` ```rust #[cfg(feature = "advanced")] async fn refresh_handler( mut auth: AuthSession, ) -> &'static str { // Force-reload user data from DB and update the cache entry auth.reload_user().await; // Bump the cache expiry without reloading data auth.update_user_expiration(); // Sync the in-memory `id` field to what is currently in the session auth.sync_user_id(); "Auth state refreshed." } ``` -------------------------------- ### Implement HasPermission Trait for Access Control Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Implement the `HasPermission` trait to enable the `Rights`-based access control system. The `has` method checks for specific permission tokens, optionally querying the database for elevated permissions. ```rust use async_trait::async_trait; use axum_session_auth::HasPermission; use sqlx::SqlitePool; #[async_trait] impl HasPermission for User { async fn has(&self, perm: &str, pool: &Option<&SqlitePool>) -> bool { // Simple in-memory check (permissions loaded during load_user): if self.permissions.contains(perm) { return true; } // Optional: fall back to a live DB query for elevated permissions if let Some(db) = pool { let row: Option<(String,)> = sqlx::query_as( "SELECT token FROM user_permissions WHERE user_id = $1 AND token = $2" ) .bind(self.id) .bind(perm) .fetch_optional(*db) .await .unwrap_or(None); return row.is_some(); } false } } ``` -------------------------------- ### AuthSession Extractor Usage Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Shows how to use the AuthSession extractor within Axum handlers to access the current user, session data, and authentication status. ```APIDOC ## `AuthSession` Extractor `AuthSession` is an Axum extractor that provides the current user, session handle, and auth management methods inside any handler. It is populated automatically by `AuthSessionLayer` on every request. ```rust use axum::http::Method; use axum_session_auth::AuthSession; use axum_session_sqlx::SessionSqlitePool; use sqlx::SqlitePool; async fn profile_handler( auth: AuthSession, ) -> String { // Access the loaded user (None only if no anonymous_user_id is configured) let user = auth.current_user.clone().unwrap_or_default(); if auth.is_authenticated() { format!("Welcome back, {}! Your ID is {}.", user.username, auth.id) } else { "You are browsing as a guest.".to_owned() } } async fn session_data_handler( auth: AuthSession, ) -> String { // The embedded `session` field gives full access to axum_session's Session API let count: usize = auth.session.get("page_views").unwrap_or(0); auth.session.set("page_views", count + 1); format!("You have visited {} times.", count + 1) } ``` ``` -------------------------------- ### Rights — Permission Rule Builder Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt `Rights` is a composable enum for constructing boolean permission expressions. It allows combining `Rights::all`, `Rights::any`, and `Rights::none` to express complex access policies. ```APIDOC ## `Rights` — Permission Rule Builder `Rights` is a composable enum for constructing boolean permission expressions. Combine `Rights::all`, `Rights::any`, and `Rights::none` to express arbitrary access policies. ```rust use axum_session_auth::Rights; // Require ALL of these permissions (AND logic) let admin_rights = Rights::all([ Rights::permission("admin:login"), Rights::permission("admin:view_dashboard"), ]); // Require AT LEAST ONE of these permissions (OR logic) let viewer_rights = Rights::any([ Rights::permission("Category::View"), Rights::permission("Admin::View"), ]); // Deny if the user holds ANY of these permissions (NOT logic) let non_banned = Rights::none([ Rights::permission("status:banned"), Rights::permission("status:suspended"), ]); // Nested composition: must be non-banned AND have at least one view right let combined = Rights::all([ non_banned, viewer_rights, ]); ``` ``` -------------------------------- ### AuthSession::logout_user Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Explains how to log out a user by removing their ID from the session and renewing the session ID. ```APIDOC ## `AuthSession::logout_user` — Log Out a User Removes the user ID from the session and renews the session ID. ```rust async fn logout_handler( auth: AuthSession, ) -> &'static str { auth.logout_user(); "You have been logged out." } ``` ``` -------------------------------- ### Access User and Session Data with AuthSession Extractor Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Use the AuthSession extractor in your handlers to access the current user, session data, and authentication status. It is automatically populated by AuthSessionLayer. ```rust use axum::http::Method; use axum_session_auth::AuthSession; use axum_session_sqlx::SessionSqlitePool; use sqlx::SqlitePool; async fn profile_handler( auth: AuthSession, ) -> String { // Access the loaded user (None only if no anonymous_user_id is configured) let user = auth.current_user.clone().unwrap_or_default(); if auth.is_authenticated() { format!("Welcome back, {}! Your ID is {}.", user.username, auth.id) } else { "You are browsing as a guest.".to_owned() } } async fn session_data_handler( auth: AuthSession, ) -> String { // The embedded `session` field gives full access to axum_session's Session API let count: usize = auth.session.get("page_views").unwrap_or(0); auth.session.set("page_views", count + 1); format!("You have visited {} times.", count + 1) } ``` -------------------------------- ### Log Out a User with AuthSession::logout_user Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Call `logout_user` to remove the user ID from the session and renew the session ID. This effectively logs the user out of the application. ```rust async fn logout_handler( auth: AuthSession, ) -> &'static str { auth.logout_user(); "You have been logged out." } ``` -------------------------------- ### AuthSession::cache_clear_user / cache_clear_all Source: https://context7.com/ascendingcreations/axumsessionauth/llms.txt Invalidates the cache for a specific user or all users. This is useful after updating user data or permissions in the database to ensure the next request reloads the information. ```APIDOC ## `AuthSession::cache_clear_user` / `cache_clear_all` — Invalidate User Cache After updating a user's data or permissions in the database, call these to force a reload on the next request. ```rust async fn update_username_handler( auth: AuthSession, // Imagine axum extractors provide new_name and target_id here ) -> &'static str { let target_id: i64 = 42; // ... perform DB update ... // sqlx::query("UPDATE users SET username = $1 WHERE id = $2") // .bind(&new_name).bind(target_id).execute(&pool).await.unwrap(); // Evict just the affected user from the shared cache auth.cache_clear_user(target_id); // Or evict every cached user (e.g., after a bulk permission change) // auth.cache_clear_all(); "User updated. Cache invalidated." } ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.