Debug School

rakesh kumar
rakesh kumar

Posted on • Edited on

Middleware-Based Route Protection in Actix-Web (Rust) – Production Ready Implementation Guide

Introduction
Middleware-Based Route Protection
What is Middleware in Actix-Web?
Why Use Middleware for Authentication
When Should You Use Middleware?
Architecture Overview
Why Middleware Order Matters
Advanced: Protect Only Some Routes
Add Role-Based Middleware (Advanced)
Security Benefits
Common Developer Mistakes
Production Best Practices

Practical coding example
Objective and MCQ

Introduction

In small projects, we protect routes by checking session inside every handler:

let user_id = ensure_login_user_id(&session)?;
Enter fullscreen mode Exit fullscreen mode

But in large applications with 50+ routes, this becomes messy and repetitive.

The professional solution is:

Middleware-Based Route Protection

Middleware automatically blocks unauthenticated users before they reach the handler.

2️⃣ What is Middleware in Actix-Web?

Middleware is a layer that runs:

Before the request reaches the handler

After the handler returns a response

Think of it like a security checkpoint before entering a building.

3️⃣Why Use Middleware for Authentication?
Without Middleware:

Every handler repeats session check

Risk of forgetting to protect a route

Code duplication

With Middleware:

Centralized protection

Cleaner handlers

More scalable

4️⃣ When Should You Use Middleware?

Use middleware when:

You have multiple protected routes

You want global authentication rules

You want consistent 401 responses

You want scalable architecture

5️⃣ Architecture Overview

Client Request
      ↓
CORS Middleware
      ↓
Session Middleware
      ↓
RequireLogin Middleware   ← 🔐 Authentication Check
      ↓
Route Handler
      ↓
Response
Enter fullscreen mode Exit fullscreen mode

6️⃣ Step 1: Create RequireLogin Middleware

Create file:

src/middleware/require_login.rs
Enter fullscreen mode Exit fullscreen mode

Full Middleware Code

use actix_service::{Service, Transform};
use actix_web::{
    dev::{ServiceRequest, ServiceResponse},
    Error, error,
};
use futures_util::future::{ready, LocalBoxFuture, Ready};
use actix_session::SessionExt;

pub struct RequireLogin;

impl<S, B> Transform<S, ServiceRequest> for RequireLogin
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = RequireLoginMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(RequireLoginMiddleware { service }))
    }
}

pub struct RequireLoginMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for RequireLoginMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let session = req.get_session();

        let is_logged_in = session
            .get::<i64>("user_id")
            .ok()
            .flatten()
            .is_some();

        if !is_logged_in {
            return Box::pin(async {
                Err(error::ErrorUnauthorized("Login required"))
            });
        }

        let fut = self.service.call(req);
        Box::pin(async move { fut.await })
    }
}
Enter fullscreen mode Exit fullscreen mode

7️⃣ Step 2: Register Middleware in main.rs

Import it:

mod middleware;
use middleware::require_login::RequireLogin;
Enter fullscreen mode Exit fullscreen mode

Apply it only to protected routes:

.service(
    web::scope("/api")
        .route("/login", web::post().to(handlers::login))
        .route("/register", web::post().to(handlers::register))
        .service(
            web::scope("")
                .wrap(RequireLogin)
                .route("/dashboard", web::get().to(handlers::dashboard))
                .route("/shops", web::get().to(handlers::list_shops_handler))
        )
)
Enter fullscreen mode Exit fullscreen mode

8️⃣ What Happens Internally?

If user is not logged in:

{
  "error": "Login required"
}
Enter fullscreen mode Exit fullscreen mode

If logged in:

Request proceeds to handler normally.

9️⃣ Cleaner Handlers Now

Before middleware:

let user_id = ensure_login_user_id(&session)?;
Enter fullscreen mode Exit fullscreen mode

After middleware:

// No login check needed
pub async fn dashboard(...) { }

Much cleaner and safer.

🔟 Why Middleware Order Matters

Order inside .wrap() is important.

Correct order:

.wrap(cors)
.wrap(SessionMiddleware::builder(...))
.wrap(RequireLogin)
Enter fullscreen mode Exit fullscreen mode

Session must come before RequireLogin.

1️⃣1️⃣ Advanced: Protect Only Some Routes

You can structure like this:

web::scope("/api")
    .route("/login", web::post().to(login))
    .service(
        web::scope("/admin")
            .wrap(RequireLogin)
            .route("/users", web::get().to(admin_users))
    )
Enter fullscreen mode Exit fullscreen mode

Now only /api/admin/* requires login.

1️⃣2️⃣ Add Role-Based Middleware (Advanced)

Instead of checking role in handler, create AdminMiddleware.

if role != "admin" {
    return Err(error::ErrorForbidden("Admin only"));
}
Enter fullscreen mode Exit fullscreen mode

This keeps authorization separate from business logic.

1️⃣3️⃣ Security Benefits

✔ Prevents accidental unprotected route
✔ Centralized security logic
✔ Easier auditing
✔ Clean code
✔ Enterprise-ready architecture

1️⃣4️⃣ Common Developer Mistakes

❌ Forgetting to add middleware
❌ Wrong middleware order
❌ Not enabling SessionMiddleware first
❌ Returning plain text errors instead of JSON
❌ Applying middleware to login route (causes login loop)

1️⃣5️⃣ Production Best Practices

Always use HTTPS

Enable .cookie_secure(true)

Use strong SESSION_KEY

Log unauthorized attempts

Combine with session timeout
Enter fullscreen mode Exit fullscreen mode

Practical coding example

Project Structure Overview
Before writing authentication logic, we must organize the project correctly. A clean folder structure makes middleware, handlers, and DB logic easy to scale.

src/
  main.rs
  config.rs
  models.rs
  handlers.rs
  handlers/
    auth.rs
    shop.rs
    vehicle.rs
  db.rs
  db/
    common.rs
    shop.rs
    vehicle.rs
Enter fullscreen mode Exit fullscreen mode

Add middleware module files

Create these files:


src/middleware/mod.rs

src/middleware/require_login.rs

src/middleware/mod.rs
pub mod require_login;
src/middleware/require_login.rs
Enter fullscreen mode Exit fullscreen mode

This is the same production-ready middleware pattern from the middleware guide: it reads the session and blocks if user_id is missing.

use actix_service::{Service, Transform};
use actix_session::SessionExt;
use actix_web::{
    dev::{ServiceRequest, ServiceResponse},
    error,
    Error,
};
use futures_util::future::{ready, LocalBoxFuture, Ready};

pub struct RequireLogin;

impl<S, B> Transform<S, ServiceRequest> for RequireLogin
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = RequireLoginMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(RequireLoginMiddleware { service }))
    }
}

pub struct RequireLoginMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for RequireLoginMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let session = req.get_session();

        let is_logged_in = session
            .get::<i64>("user_id")
            .ok()
            .flatten()
            .is_some();

        if !is_logged_in {
            return Box::pin(async { Err(error::ErrorUnauthorized("Login required")) });
        }

        let fut = self.service.call(req);
        Box::pin(async move { fut.await })
    }
}
Enter fullscreen mode Exit fullscreen mode

middleware/mod.rs

pub mod auth_middleware;
pub mod role_middleware;

pub use auth_middleware::AuthMiddleware;
pub use role_middleware::require_roles;
Enter fullscreen mode Exit fullscreen mode

2) Wire it in main.rs (your project structure)

In your main.rs you already have:

mod db;
mod handlers;
mod layout;
mod models;

Enter fullscreen mode Exit fullscreen mode

Add:

mod middleware;
use middleware::require_login::RequireLogin;
Enter fullscreen mode Exit fullscreen mode

Now apply middleware main.rs

web::scope("/api")
    .route("/login", web::post().to(handlers::login)) // public
    .route("/logout", web::post().to(handlers::logout)) // public
    .service(
        web::scope("")
            .wrap(middleware::AuthMiddleware) // auth check happens here first
            .route("/me", web::get().to(handlers::me))
            .route("/dashboard", web::get().to(handlers::dashboard))
            .route("/shops", web::get().to(handlers::list_shops_handler))
    )

Enter fullscreen mode Exit fullscreen mode

Login creates session (required by AuthMiddleware)

session.insert("username", user.username.clone())?;
session.insert("user_id", user.id)?; // AuthMiddleware uses this
session.insert("role", role.clone())?;
Enter fullscreen mode Exit fullscreen mode

AuthMiddleware runs before handler

fn is_logged_in(req: &ServiceRequest) -> bool {
    req.get_session().get::<i64>("user_id").ok().flatten().is_some()
}

if !is_logged_in(&req) {
    return 401 Unauthorized;
}
Enter fullscreen mode Exit fullscreen mode

What to remove from handlers now
✅ Remove this line from all protected handlers

ensure_login_user_id(&session)?;
Enter fullscreen mode Exit fullscreen mode

Protect only some areas (admin scope example)

If you want /api/admin/* protected but other /api/* routes public, wrap only that scope (same pattern as the guide).

.service(
    web::scope("/api")
        .route("/login", web::post().to(handlers::login))
        .service(
            web::scope("/admin")
                .wrap(RequireLogin)
                .route("/users", web::get().to(handlers::admin_users))
        )
)
Enter fullscreen mode Exit fullscreen mode

====================ANOTHER EXAMPLE=======================
// src/middleware.rs

PRACTICAL CODING EXAMPLE


// auth_middleware.rs
use std::future::{ready, Ready};
use std::rc::Rc;

use actix_session::SessionExt;
use actix_web::{
    body::{EitherBody, MessageBody},
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    Error, HttpResponse,
};
use futures_util::future::LocalBoxFuture;

fn is_logged_in(req: &ServiceRequest) -> bool {
    req.get_session()
        .get::<i64>("user_id")
        .ok()
        .flatten()
        .is_some()
}

pub struct AuthMiddleware;

impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: MessageBody + 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type InitError = ();
    type Transform = AuthMiddlewareService<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(AuthMiddlewareService {
            service: Rc::new(service),
        }))
    }
}

pub struct AuthMiddlewareService<S> {
    service: Rc<S>,
}

impl<S, B> Service<ServiceRequest> for AuthMiddlewareService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    B: MessageBody + 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let service = self.service.clone();
        Box::pin(async move {
            if !is_logged_in(&req) {
                let response = HttpResponse::Unauthorized().body("Login required");
                return Ok(req.into_response(response).map_into_right_body());
            }

            let response = service.call(req).await?;
            Ok(response.map_into_left_body())
        })
    }
}

Enter fullscreen mode Exit fullscreen mode

main.rs

// in main.rs
mod middleware;
Enter fullscreen mode Exit fullscreen mode

. Route registration in main.rs
Public routes are outside auth middleware. Protected routes are inside .wrap(AuthMiddleware).

web::scope("/api")
    .route("/login", web::post().to(handlers::login)) // public
    .route("/logout", web::post().to(handlers::logout)) // public
    .service(
        web::scope("")
            .wrap(middleware::AuthMiddleware) // auth check happens here first
            .route("/me", web::get().to(handlers::me))
            .route("/dashboard", web::get().to(handlers::dashboard))
            .route("/shops", web::get().to(handlers::list_shops_handler))
    )
Enter fullscreen mode Exit fullscreen mode

Login creates session (required by AuthMiddleware)
/api/login handler checks DB user, then sets session keys:

session.insert("username", user.username.clone())?;
session.insert("user_id", user.id)?; // AuthMiddleware uses this
session.insert("role", role.clone())?;
Enter fullscreen mode Exit fullscreen mode

AuthMiddleware runs before handler

For protected routes, middleware executes first:

fn is_logged_in(req: &ServiceRequest) -> bool {
    req.get_session().get::<i64>("user_id").ok().flatten().is_some()
}

if !is_logged_in(&req) {
    return 401 Unauthorized;
}
Enter fullscreen mode Exit fullscreen mode

Handler runs after middleware passes
Example shops handler:

let user_id = session_user_id(&session).ok_or_else(|| error::ErrorUnauthorized("Login required"))?;
let shops = list_shops_by_vendor(&state.pool, vendor_id).await?;
Enter fullscreen mode Exit fullscreen mode

DB layer executes SQL
Handler calls DB function, DB runs SQL via sqlx, returns rows.

Request chain (Auth only):

Client -> main.rs route scope -> AuthMiddleware -> handler -> db -> handler response -> client
Enter fullscreen mode Exit fullscreen mode

If not logged in:

Client -> main.rs route scope -> AuthMiddleware -> 401
Enter fullscreen mode Exit fullscreen mode
fn is_logged_in(req: &ServiceRequest) -> bool {
    req.get_session()
        .get::<i64>("user_id")
        .ok()
        .flatten()
        .is_some()
}

Enter fullscreen mode Exit fullscreen mode

above fun is_logged_in in alternative way


fn get_user_from_session() -> Result<Option<i64>, &'static str> {
    Ok(Some(55))
}

fn main() {

    let session_result = get_user_from_session();

    println!("Step 1: {:?}", session_result);

    let ok_value = session_result.ok();

    println!("Step 2 (ok): {:?}", ok_value);

    let flattened = ok_value.flatten();

    println!("Step 3 (flatten): {:?}", flattened);

    let logged_in = flattened.is_some();

    println!("Step 4 (is_some): {}", logged_in);

}
Enter fullscreen mode Exit fullscreen mode

Output

Step 1: Ok(Some(55))
Step 2 (ok): Some(Some(55))
Step 3 (flatten): Some(55)
Step 4 (is_some): true
Enter fullscreen mode Exit fullscreen mode

Objective and MCQ

Read this blog carefully

understanding-the-two-layer-architecture-of-actix-middleware-transform-vs-service

What is the main purpose of middleware in Actix-web?

A. Handle database queries
B. Intercept requests before they reach handlers
C. Compile Rust code faster
D. Replace HTTP responses

✅ Answer: B
Middleware can inspect or reject requests before they reach handlers.

  1. Middleware in Actix-web is typically implemented using which two traits?

A. Future and Async
B. Transform and Service
C. Handler and Request
D. Router and Scope

✅ Answer: B
Middleware is created using Transform (factory) and Service (execution) traits.

  1. Which Actix type represents an incoming HTTP request inside middleware?

A. HttpRequest
B. ServiceRequest
C. RequestContext
D. RequestBody

✅ Answer: B
ServiceRequest contains the request data processed by middleware.

  1. Which type represents the response returned from middleware or handlers?

A. HttpResponse
B. ServiceResponse
C. ResponseBody
D. HandlerResponse

✅ Answer: B
ServiceResponse wraps both request and response objects.

  1. What happens if middleware decides to block a request?

A. Request continues to handler
B. Middleware returns its own HTTP response
C. Server restarts
D. Request is ignored

✅ Answer: B
Middleware can stop processing and return an early response.

  1. Which HTTP response is commonly returned when authentication fails?

A. 200 OK
B. 401 Unauthorized
C. 201 Created
D. 302 Redirect

✅ Answer: B

  1. Which method in middleware processes each incoming request?

A. execute()
B. handle()
C. call()
D. run()

✅ Answer: C
The call() function is where request processing occurs.

  1. What does forward_ready! macro do in middleware?

A. Sends response to client
B. Forwards readiness check to inner service
C. Creates a request object
D. Serializes JSON

✅ Answer: B

  1. Why does middleware often use generics like ?

A. To support different services and response body types
B. To reduce code size
C. To store database values
D. To handle logging

✅ Answer: A
Generics allow middleware to work with any service and body type.

  1. What does S usually represent in middleware generic parameters?

A. Session object
B. Inner service being wrapped
C. Server configuration
D. Security token

✅ Answer: B

  1. Which trait must response body type implement in Actix middleware?

A. Clone
B. Serialize
C. MessageBody
D. Future

✅ Answer: C

  1. What does the middleware authentication function typically check?

A. Database schema
B. Request headers or session tokens
C. Compiler version
D. JSON parsing

✅ Answer: B

  1. What architectural pattern describes middleware execution order?

A. MVC pattern
B. Onion / pipeline pattern
C. Observer pattern
D. Singleton pattern

✅ Answer: B
Middleware layers wrap around handlers like an onion structure.

  1. In route protection middleware, what usually happens when the user is authenticated?

A. Request stops
B. Middleware calls the inner service
C. Server logs out user
D. Database connection closes

✅ Answer: B

  1. Which Actix function allows attaching middleware to an application?

A. attach()
B. wrap()
C. register()
D. bind()

✅ Answer: B
App::wrap() attaches middleware.

  1. Which type is commonly used to represent asynchronous middleware results?

A. Result
B. LocalBoxFuture
C. AsyncResponse
D. FutureResult

✅ Answer: B

  1. What is the role of Transform trait in middleware?

A. Handle request execution
B. Create middleware service instances
C. Manage database connections
D. Serialize responses

✅ Answer: B

  1. Which scenario is a common use case for route protection middleware?

A. Image processing
B. Authentication and authorization
C. File compression
D. Code compilation

✅ Answer: B

  1. Which middleware logic is typically executed before the handler?

A. Response serialization
B. Authentication check
C. Database backup
D. File upload

✅ Answer: B

  1. What is the main advantage of middleware-based route protection?

A. Reduces database size
B. Centralizes authentication logic
C. Removes need for handlers
D. Increases CPU usage

✅ Answer: B
Middleware centralizes authentication and security checks for multiple routes.

Top comments (0)