Axum Login
Authentication Library
Axum Login
Overview
Axum Login is a dedicated authentication and authorization library for "Axum," Rust's fast asynchronous web framework. It provides user identification, authentication, and authorization features, achieving high performance through modern session management and middleware-based implementation. It features integration with Tower Services, type safety, and async-first design, with active development continuing in 2025.
Details
Axum Login is an authentication and authorization library specialized for the Axum framework. It offers the following key characteristics:
- Complete Axum Integration: Seamless integration with the Axum ecosystem
- Session Management: Flexible session management through tower-sessions
- Type-Safe Authentication: Compile-time safety leveraging Rust's type system
- Middleware-Based: Authentication layer using Tower middleware patterns
- Async Support: Complete asynchronous processing for high performance
- Customizable: Support for custom authentication logic and providers
Pros and Cons
Pros
- Rust's type safety detects authentication errors at compile time
- High performance and scalability through asynchronous processing
- High development efficiency with complete Axum ecosystem integration
- Flexible authentication flow through Tower middleware patterns
- Benefits of memory safety and zero-cost abstractions
- Aligned with modern Rust async programming paradigms
Cons
- Cannot be used outside the Axum framework
- High learning curve for Rust, difficult for beginners
- Limited library ecosystem compared to other languages
- Configuration and setup can become complex
- Relatively new with fewer case studies and information available
- Steeper learning curve for developers unfamiliar with Rust ecosystem
Reference Pages
Code Examples
Adding Dependencies to Cargo.toml
[dependencies]
axum = "0.7"
axum-login = "0.17.0"
tower = "0.4"
tower-sessions = "0.12"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
User Model and Backend Definition
use axum_login::{AuthUser, AuthnBackend, UserId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use async_trait::async_trait;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: i64,
pub username: String,
pub password: String,
}
impl AuthUser for User {
type Id = i64;
fn id(&self) -> Self::Id {
self.id
}
fn session_auth_hash(&self) -> &[u8] {
self.password.as_bytes()
}
}
// Simple memory-based backend (example)
#[derive(Debug, Clone)]
pub struct Backend {
users: HashMap<i64, User>,
}
impl Backend {
pub fn new() -> Self {
let mut users = HashMap::new();
users.insert(
1,
User {
id: 1,
username: "admin".to_string(),
password: "password".to_string(), // Must be hashed in production
},
);
Self { users }
}
}
#[async_trait]
impl AuthnBackend for Backend {
type User = User;
type Credentials = Credentials;
type Error = std::convert::Infallible;
async fn authenticate(
&self,
creds: Self::Credentials,
) -> Result<Option<Self::User>, Self::Error> {
let user = self
.users
.values()
.find(|user| user.username == creds.username && user.password == creds.password)
.cloned();
Ok(user)
}
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
Ok(self.users.get(user_id).cloned())
}
}
#[derive(Debug, Deserialize)]
pub struct Credentials {
pub username: String,
pub password: String,
}
Router and Middleware Configuration
use axum::{
routing::{get, post},
Router,
};
use axum_login::{AuthManagerLayer, AuthManagerLayerBuilder};
use tower_sessions::{MemoryStore, SessionManagerLayer};
pub type AuthSession = axum_login::AuthSession<Backend>;
pub fn create_router() -> Router {
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false); // Set to false for non-HTTPS
let backend = Backend::new();
let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build();
Router::new()
.route("/", get(home))
.route("/login", get(login_form).post(login))
.route("/logout", post(logout))
.route("/protected", get(protected))
.layer(auth_layer)
}
async fn home() -> &'static str {
"Welcome to Axum Login Demo!"
}
Login Processing
use axum::{
extract::Form,
response::{Html, Redirect},
};
async fn login_form() -> Html<&'static str> {
Html(r#"
<form method="post" action="/login">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
"#)
}
async fn login(mut auth: AuthSession, Form(creds): Form<Credentials>) -> Result<Redirect, String> {
let user = match auth.authenticate(creds).await {
Ok(Some(user)) => user,
Ok(None) => return Err("Invalid credentials".to_string()),
Err(_) => return Err("Authentication error".to_string()),
};
if auth.login(&user).await.is_err() {
return Err("Failed to login user".to_string());
}
Ok(Redirect::to("/protected"))
}
async fn logout(mut auth: AuthSession) -> Redirect {
match auth.logout().await {
Ok(_) => Redirect::to("/"),
Err(_) => Redirect::to("/"),
}
}
Protected Route Implementation
use axum::response::Html;
async fn protected(auth: AuthSession) -> Result<Html<String>, Redirect> {
match auth.user {
Some(user) => {
let content = format!(
r#"
<h1>Protected Page</h1>
<p>Welcome, {}!</p>
<form method="post" action="/logout">
<button type="submit">Logout</button>
</form>
"#,
user.username
);
Ok(Html(content))
}
None => Err(Redirect::to("/login")),
}
}
Custom Authentication Middleware
use axum::{
extract::Request,
http::StatusCode,
middleware::Next,
response::Response,
};
pub async fn auth_required(
auth: AuthSession,
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
match auth.user {
Some(_) => Ok(next.run(request).await),
None => Err(StatusCode::UNAUTHORIZED),
}
}
// Usage example in router
fn protected_routes() -> Router {
Router::new()
.route("/admin", get(admin_panel))
.route("/dashboard", get(dashboard))
.layer(axum::middleware::from_fn(auth_required))
}
Server Startup in Main Function
use axum::serve;
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = create_router();
let listener = TcpListener::bind("127.0.0.1:3000").await?;
println!("Server running on http://127.0.0.1:3000");
serve(listener, app).await?;
Ok(())
}