Axum Login

authentication-libraryRustAxumsession-managementJWTOAuthasyncsecurity

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(())
}