Role-Based Access Control (RBAC) is a crucial security mechanism that restricts system access to authorized users based on their roles. Implementing RBAC in web applications enhances security by ensuring users can only perform actions permitted by their roles. In Rust, the Axum framework provides a flexible foundation for building web services, and with the right libraries, implementing RBAC becomes straightforward.

Understanding Axum and Authorization

Axum is a modern, asynchronous web framework for Rust that emphasizes modularity and type safety. It allows developers to build scalable web services with ease. For authorization, Axum can be combined with middleware and extractors to manage user roles and permissions effectively.

Setting Up the Project

To start, create a new Rust project and add necessary dependencies:

cargo new axum-rbac
cd axum-rbac

Update Cargo.toml with dependencies:

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tower-http = { version = "0.3", features = ["trace"] }

Defining User Roles and Data Structures

First, define roles and user data structures:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
enum Role {
    Admin,
    User,
    Guest,
}

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    role: Role,
}

Implementing Role-Based Authorization Middleware

Next, create middleware to check user roles before granting access:

use tower_http::auth::RequireAuthorizationLayer;
use axum::{
    async_trait,
    extract::{FromRequest, RequestParts},
    middleware::Next,
    response::Response,
    Router,
};
use std::sync::Arc;

struct AuthUser(Arc);

#[async_trait]
impl FromRequest for AuthUser
where
    B: Send,
{
    type Rejection = Response;

    async fn from_request(req: &mut RequestParts) -> Result {
        // In real scenario, extract user info from headers or session
        // Here, mock a user for demonstration
        let user = User {
            id: 1,
            name: "Alice".to_string(),
            role: Role::Admin,
        };
        Ok(AuthUser(Arc::new(user)))
    }
}

fn role_required(required_role: Role) -> impl tower::Layer<
    Service = impl tower::Service<
        Request = axum::http::Request,
        Response = Response,
        Error = impl std::error::Error + Send + Sync,
        Future = impl std::future::Future>,
    > + Clone,
> + Clone {
    tower::layer::layer_fn(move |service| {
        tower::service_fn(move |req| {
            let user = req.extensions().get::().unwrap();
            if user.0.role == required_role {
                service.call(req)
            } else {
                async {
                    Ok(Response::builder()
                        .status(403)
                        .body(axum::body::Body::from("Forbidden"))
                        .unwrap())
                }
            }
        })
    })
}

Creating Protected Routes

Define routes that require specific roles:

use axum::{routing::get, Router};

async fn admin_only_handler() -> &'static str {
    "Welcome, Admin!"
}

async fn user_handler() -> &'static str {
    "Hello, User!"
}

fn app() -> Router {
    Router::new()
        .route("/admin", get(admin_only_handler))
        .route("/user", get(user_handler))
        // Apply middleware
        .layer(role_required(Role::Admin))
}

Running the Application

Set up the main function to run the server:

use axum::Server;

#[tokio::main]
async fn main() {
    let app = app();

    println!("Listening on http://127.0.0.1:3000");
    Server::bind(&"127.0.0.1:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

Conclusion

Implementing RBAC with Axum in Rust involves defining roles, creating middleware to enforce permissions, and protecting routes accordingly. This approach enhances security and maintains clear access boundaries within your web applications. As you develop more complex systems, consider expanding this pattern with dynamic role management and integration with authentication services.