Authentication

axum-admin ships two auth backends out of the box and lets you plug in your own via the AdminAuth trait.

The AdminAuth Trait

#[async_trait]
pub trait AdminAuth: Send + Sync {
    async fn authenticate(
        &self,
        username: &str,
        password: &str,
    ) -> Result<AdminUser, AdminError>;

    async fn get_session(&self, session_id: &str) -> Result<Option<AdminUser>, AdminError>;
}

The framework calls authenticate on login and get_session on every protected request. Both return an AdminUser:

pub struct AdminUser {
    pub username: String,
    pub session_id: String,
    /// true = bypasses all permission checks (superuser access)
    pub is_superuser: bool,
}

DefaultAdminAuth

DefaultAdminAuth is an in-memory backend. Credentials are configured at startup; sessions live for the process lifetime. Passwords are hashed with bcrypt.

use axum_admin::auth::DefaultAdminAuth;

let auth = DefaultAdminAuth::new()
    .add_user("admin", "s3cret");

AdminApp::new()
    .auth(Box::new(auth))
    // ...

add_user is builder-style and can be chained for multiple users. Every user created through DefaultAdminAuth is implicitly a superuser — it bypasses all permission checks.

This backend is suitable for local development and single-user deployments. It has no persistence: users and sessions are lost on restart.

SeaOrmAdminAuth

SeaOrmAdminAuth stores users and sessions in PostgreSQL and integrates Casbin for RBAC. It requires the seaorm feature flag.

Setup

use axum_admin::adapters::seaorm_auth::SeaOrmAdminAuth;

let auth = SeaOrmAdminAuth::new(db.clone()).await?;

// Seed a default user only when the users table is empty.
auth.ensure_user("admin", "change-me").await?;

AdminApp::new()
    .seaorm_auth(auth)
    // ...

SeaOrmAdminAuth::new runs database migrations automatically (idempotent). It creates the auth_users, auth_sessions, and Casbin policy tables.

ensure_user

pub async fn ensure_user(&self, username: &str, password: &str) -> Result<(), AdminError>

Creates a user with is_superuser = true and assigns the admin role only if no users exist yet. Safe to call on every application startup.

create_user

pub async fn create_user(
    &self,
    username: &str,
    password: &str,
    is_superuser: bool,
) -> Result<(), AdminError>

Creates a user unconditionally. Passwords are hashed with Argon2.

change_password

pub async fn change_password(
    &self,
    username: &str,
    old_password: &str,
    new_password: &str,
) -> Result<(), AdminError>

Verifies the old password before storing the new hash.

Wiring with seaorm_auth

AdminApp::seaorm_auth is a convenience method that sets both the auth backend and the Casbin enforcer in one call:

pub fn seaorm_auth(mut self, auth: SeaOrmAdminAuth) -> Self

When .seaorm_auth() is used, the Users and Roles management pages appear automatically in the sidebar navigation.

Custom Auth Backend

Implement AdminAuth on any struct and pass it with .auth():

use axum_admin::auth::{AdminAuth, AdminUser};
use axum_admin::error::AdminError;
use async_trait::async_trait;

pub struct MyAuth;

#[async_trait]
impl AdminAuth for MyAuth {
    async fn authenticate(
        &self,
        username: &str,
        password: &str,
    ) -> Result<AdminUser, AdminError> {
        // verify credentials, create a session record, return AdminUser
        todo!()
    }

    async fn get_session(&self, session_id: &str) -> Result<Option<AdminUser>, AdminError> {
        // look up session by ID, return None if expired or missing
        todo!()
    }
}

AdminApp::new()
    .auth(Box::new(MyAuth))

Custom backends always use the basic .auth() path. The Casbin enforcer and the Users/Roles nav pages are only available through .seaorm_auth().