Debug School

rakesh kumar
rakesh kumar

Posted on

Role-Based Authorization System (RBAC) in Actix-Web (Rust) – Enterprise Implementation Guide

Introduction
Authentication vs Authorization
What is Role-Based Authorization?
Why RBAC is Important
When Should You Use RBAC?
System Architecture Overview
Store Role in Session During Login
Create Role Guard Helper
Protect Admin Route
Protect Vendor Route
Role-Based Scope Grouping
Create Admin Middleware (Advanced)
Prevent Privilege Escalation
Database-Level Role Enforcement (Extra Secure)
Multi-Role Access Example
Real Production Role Example
Common RBAC Mistakes
Best Practices
Security Flow
RBAC vs Simple Login Check

Introduction

Authentication answers:

`“Who are you?”`
Enter fullscreen mode Exit fullscreen mode

Authorization answers:

`“What are you allowed to do?”`
Enter fullscreen mode Exit fullscreen mode

In real-world applications, just checking if a user is logged in is not enough. We must also control what actions each user can perform.

This is where Role-Based Access Control (RBAC) comes in.

In this guide, we will build a production-ready RBAC system using:

Actix-Web

Session-based authentication

Middleware + guard helpers

Clean architecture
Enter fullscreen mode Exit fullscreen mode

2️⃣ What is Role-Based Authorization?

RBAC is a system where users are assigned roles, and roles define permissions.

Why RBAC is Important

Without RBAC:

Any logged-in user may access admin routes

Data leaks may happen

Privilege escalation becomes possible
Enter fullscreen mode Exit fullscreen mode

With RBAC:

Clear separation of responsibilities

Secure system design

Easy to scale for large applications

Enter fullscreen mode Exit fullscreen mode

4️⃣When Should You Use RBAC?

Use RBAC when:

You have multiple user types

You build admin panels

You build SaaS applications

You handle sensitive business logic
Enter fullscreen mode Exit fullscreen mode

5️⃣ System Architecture Overview

User Login
    ↓
Session stores user_id + role
    ↓
Middleware/Guard checks role
    ↓
Access granted or denied
Enter fullscreen mode Exit fullscreen mode

6️⃣ Step 1: Store Role in Session During Login

Inside login handler:

session.insert("role", user.role.clone())?;
Enter fullscreen mode Exit fullscreen mode

Now every request carries role info in session.

7️⃣ Step 2: Create Role Guard Helper

Create in src/handlers/common.rs

use actix_session::Session;
use actix_web::{error, Result};

pub fn require_role(session: &Session, allowed_roles: &[&str]) -> Result<()> {
    let role = session
        .get::<String>("role")
        .map_err(error::ErrorInternalServerError)?
        .unwrap_or_default();

    if !allowed_roles.contains(&role.as_str()) {
        return Err(error::ErrorForbidden("Access denied"));
    }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

8️⃣ Step 3: Protect Admin Route

Example:

pub async fn admin_dashboard(
    session: Session,
) -> Result<HttpResponse> {

    require_role(&session, &["admin"])?;

    Ok(HttpResponse::Ok().json(ApiResponse {
        message: "Admin Dashboard".to_string(),
        data: true,
    }))
}
Enter fullscreen mode Exit fullscreen mode

9️⃣ Step 4: Protect Vendor Route

require_role(&session, &["admin", "vendor"])?;
Enter fullscreen mode Exit fullscreen mode

This allows both admin and vendor.

🔟 Step 5: Role-Based Scope Grouping

Instead of checking role in every handler, create role middleware.

Example:

web::scope("/api/admin")
    .wrap(RequireLogin)
    .wrap(RequireAdmin)
Enter fullscreen mode Exit fullscreen mode

Now all /api/admin/* routes are admin-only.

Create Admin Middleware (Advanced)

pub struct RequireAdmin;

impl<S, B> Transform<S, ServiceRequest> for RequireAdmin

Inside call():

let role = session.get::<String>("role")?;
if role != Some("admin".to_string()) {
    return Err(error::ErrorForbidden("Admin only"));
}
Enter fullscreen mode Exit fullscreen mode

1️⃣2️⃣ Prevent Privilege Escalation

Never trust frontend role input.

Wrong:

let role = body.role;
Enter fullscreen mode Exit fullscreen mode

Correct:
Always read role from session:

let role = session.get::<String>("role")?;
Enter fullscreen mode Exit fullscreen mode

1️⃣3️⃣ Database-Level Role Enforcement (Extra Secure)

For sensitive operations, double-check role in DB.


let db_role = sqlx::query_scalar!(
    "SELECT role FROM users WHERE id = ?",
    user_id
).fetch_one(&state.pool).await?;
Enter fullscreen mode Exit fullscreen mode

This prevents tampering.

1️⃣4️⃣ Multi-Role Access Example

require_role(&session, &["admin", "manager"])?;
Enter fullscreen mode Exit fullscreen mode

Very flexible design.

1️⃣5️⃣ Real Production Role Example

SaaS System Roles:

Super Admin

Admin

Vendor

Staff

Customer

Each role has:

Different dashboards

Different APIs

Different data visibility

1️⃣6️⃣ Common RBAC Mistakes

❌ Checking role only on frontend
❌ Forgetting to protect one route
❌ Hardcoding role logic inside business code
❌ Mixing authentication and authorization logic

1️⃣7️⃣ Best Practices

Always check role server-side

Keep authorization logic separate

Use middleware for scalability

Log forbidden access attempts
Enter fullscreen mode Exit fullscreen mode

1️⃣8️⃣ Security Flow

Login → Session(role stored)
        ↓
User hits /api/admin/users
        ↓
Middleware checks role
        ↓
Allowed or 403 Forbidden
Enter fullscreen mode Exit fullscreen mode

RBAC vs Simple Login Check

practical scenario

Top comments (0)