RBAC

axum-admin uses Casbin for role-based access control. RBAC is only available when the seaorm feature is enabled and the app is configured with .seaorm_auth().

How It Works

The permission model follows the pattern (subject, object, action):

  • subject — a role name stored internally as role:<name> (e.g. role:admin)
  • object — an entity name (e.g. posts)
  • action — one of view, create, edit, delete

Superusers (is_superuser = true) bypass all Casbin checks. For regular users, every request checks that the user's assigned role has the required (entity, action) pair.

Built-in Roles

seed_roles pre-populates two roles for every registered entity:

  • adminview, create, edit, delete on all entities
  • viewerview only on all entities
pub async fn seed_roles(&self, entity_names: &[String]) -> Result<(), AdminError>

Call this after all entities are registered. The method is idempotent — it skips rules that already exist. The AdminApp::seaorm_auth builder calls seed_roles automatically when building the router.

Assigning Roles

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

Assigns a role to a user. A user has exactly one role at a time — any previous role is removed first.

auth.assign_role("alice", "viewer").await?;
auth.assign_role("bob", "admin").await?;

Role Management API

create_role

pub async fn create_role(
    &self,
    name: &str,
    permissions: &[(String, String)],
) -> Result<(), AdminError>

Creates a role with a specific set of (entity, action) pairs. Returns AdminError::Conflict if the role already exists.

Action strings: create_role takes raw Casbin action strings — "view", "create", "edit", or "delete". The entity-level guards (require_view, require_edit, etc.) automatically check entity_name.action against the Casbin policy; you never need to write the dotted form yourself when using those helpers.

auth.create_role("editor", &[
    ("posts".to_string(), "view".to_string()),
    ("posts".to_string(), "edit".to_string()),
]).await?;

get_role_permissions

pub fn get_role_permissions(&self, name: &str) -> Vec<(String, String)>

Returns all (entity, action) pairs assigned to the role.

update_role_permissions

pub async fn update_role_permissions(
    &self,
    name: &str,
    permissions: &[(String, String)],
) -> Result<(), AdminError>

Replaces the full permission set for the role.

delete_role

pub async fn delete_role(&self, name: &str) -> Result<(), AdminError>

Deletes the role and all its policies. Returns AdminError::Conflict if any users are currently assigned to it.

list_roles

pub fn list_roles(&self) -> Vec<String>

Returns all role names currently in the policy store (without the role: prefix).

get_user_role

pub fn get_user_role(&self, username: &str) -> Option<String>

Returns the role currently assigned to the user, or None if unassigned.

Entity-Level Permission Guards

Define which permission is required for each operation on an entity:

EntityAdmin::from_entity::<posts::Entity>("posts")
    .require_view("posts.view")
    .require_create("posts.create")
    .require_edit("posts.edit")
    .require_delete("posts.delete")

Or use the shortcut to guard all four actions with the same permission string:

.require_role("posts.admin")

When the permission string matches the entity.action pattern (e.g. "posts.view"), Casbin checks (username, posts, view). If no permission string is set, the framework auto-derives entity_name.action when an enforcer is present.

Accessing the Enforcer

The Casbin enforcer is exposed for advanced use:

pub fn enforcer(&self) -> Arc<tokio::sync::RwLock<casbin::Enforcer>>
let enforcer = auth.enforcer();
let guard = enforcer.read().await;
// use casbin CoreApi / MgmtApi / RbacApi directly

Startup Recipe

let auth = SeaOrmAdminAuth::new(db.clone()).await?;
auth.ensure_user("admin", "change-me").await?;

let app = AdminApp::new()
    .title("My App")
    .seaorm_auth(auth)
    .register(
        EntityAdmin::from_entity::<posts::Entity>("posts")
            .require_view("posts.view")
            .require_edit("posts.edit")
    )
    .into_router();

seed_roles is called automatically inside .into_router(), so the admin and viewer roles are available immediately after startup.