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)?;
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
6️⃣ Step 1: Create RequireLogin Middleware
Create file:
src/middleware/require_login.rs
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 })
}
}
7️⃣ Step 2: Register Middleware in main.rs
Import it:
mod middleware;
use middleware::require_login::RequireLogin;
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))
)
)
8️⃣ What Happens Internally?
If user is not logged in:
{
"error": "Login required"
}
If logged in:
Request proceeds to handler normally.
9️⃣ Cleaner Handlers Now
Before middleware:
let user_id = ensure_login_user_id(&session)?;
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)
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))
)
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"));
}
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
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
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
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 })
}
}
middleware/mod.rs
pub mod auth_middleware;
pub mod role_middleware;
pub use auth_middleware::AuthMiddleware;
pub use role_middleware::require_roles;
2) Wire it in main.rs (your project structure)
In your main.rs you already have:
mod db;
mod handlers;
mod layout;
mod models;
Add:
mod middleware;
use middleware::require_login::RequireLogin;
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))
)
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())?;
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;
}
What to remove from handlers now
✅ Remove this line from all protected handlers
ensure_login_user_id(&session)?;
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))
)
)
====================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())
})
}
}
main.rs
// in main.rs
mod middleware;
. 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))
)
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())?;
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;
}
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?;
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
If not logged in:
Client -> main.rs route scope -> AuthMiddleware -> 401
fn is_logged_in(req: &ServiceRequest) -> bool {
req.get_session()
.get::<i64>("user_id")
.ok()
.flatten()
.is_some()
}
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);
}
Output
Step 1: Ok(Some(55))
Step 2 (ok): Some(Some(55))
Step 3 (flatten): Some(55)
Step 4 (is_some): true
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.
- 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.
- 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.
- 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.
- 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.
- Which HTTP response is commonly returned when authentication fails?
A. 200 OK
B. 401 Unauthorized
C. 201 Created
D. 302 Redirect
✅ Answer: B
- 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.
- 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
- 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.
- 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
- Which trait must response body type implement in Actix middleware?
A. Clone
B. Serialize
C. MessageBody
D. Future
✅ Answer: C
- 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
- 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.
- 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
- Which Actix function allows attaching middleware to an application?
A. attach()
B. wrap()
C. register()
D. bind()
✅ Answer: B
App::wrap() attaches middleware.
- Which type is commonly used to represent asynchronous middleware results?
A. Result
B. LocalBoxFuture
C. AsyncResponse
D. FutureResult
✅ Answer: B
- 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
- 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
- Which middleware logic is typically executed before the handler?
A. Response serialization
B. Authentication check
C. Database backup
D. File upload
✅ Answer: B
- 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)