Skip to main content
Once a request has passed through the AuthLayer, you can enforce specific access control policies using Permission Layers. These layers reject unauthorized requests with a 403 Forbidden status code before they ever reach your business logic. Note: The AuthLayer MUST be applied before any Permission Layers.

Basic Scope Checks

Wacht supports Role-Based Access Control (RBAC) at two scopes: Organization and Workspace. The SDK provides shorthand layer constructors for both.
use axum::{Router, routing::{get, post}};
use wacht::middleware::{AuthLayer, PermissionLayer};

let app = Router::new()
    .nest("/api/projects", Router::new()
        // Requires the user to have 'projects:read' in their active Workspace
        .route("/", get(list_projects))
        .layer(PermissionLayer::workspace("projects:read"))
        
        // Let's create an even stricter requirement for creation
        .route("/new", post(create_project))
        .layer(PermissionLayer::workspace("projects:write"))
    )
    // The AuthLayer must be applied last (so it executes first!)
    .layer(AuthLayer::new());

Complex Logic

You can enforce more complex logic using MultiplePermissionLayers and RequireAnyPermissionLayer.

Requiring ALL Permissions (AND)

If an endpoint requires a combination of permissions, use MultiplePermissionLayers::all().
use wacht::middleware::{MultiplePermissionLayers, PermissionScope};

let strict_admin_layer = MultiplePermissionLayers::all(vec![
    ("admin:users", PermissionScope::Organization),
    ("admin:billing", PermissionScope::Organization),
]);

Requiring ANY Permission (OR)

If an endpoint can be accessed by multiple different roles, use MultiplePermissionLayers::any() or the RequireAnyPermissionLayer directly.
use wacht::middleware::{RequireAnyPermissionLayer, PermissionScope};

let content_manager_layer = RequireAnyPermissionLayer::new(vec![
    ("content:write", PermissionScope::Workspace),
    ("content:admin", PermissionScope::Workspace),
]);

Layer Ordering is Critical

Axum executes layers starting from the one applied last. Because Permission layers rely on the context injected by the AuthLayer, you must call .layer(AuthLayer::new()) after your permission layers.
// ✅ CORRECT: Authentication runs first, then permissions
let app = Router::new()
    .route("/admin", get(admin_handler))
    .layer(PermissionLayer::organization("admin:access")) 
    .layer(AuthLayer::new());                             

// ❌ INCORRECT: Will panic or fail because AuthContext is missing
let app = Router::new()
    .route("/admin", get(admin_handler))
    .layer(AuthLayer::new())                              
    .layer(PermissionLayer::organization("admin:access"));
If you only need to check permissions inside the handler body, or need to decide dynamically based on a path parameter, use the RequireAuth extractor directly instead.