In modern web development, securing your application with robust authorization mechanisms is crucial. The Actix Web framework for Rust provides a flexible way to implement custom middleware for authorization purposes. This guide walks you through building a custom authorization middleware in Actix Web.

Understanding Middleware in Actix Web

Middleware in Actix Web acts as a layer that intercepts requests and responses. It allows you to perform operations such as authentication, logging, or modifications before passing control to your handlers. Creating custom middleware enables tailored security solutions suited to your application's needs.

Setting Up Your Project

Start by creating a new Rust project and adding the necessary dependencies. Ensure you include actix-web in your Cargo.toml file:

cargo new auth-middleware
cd auth-middleware
[dependencies]
actix-web = "4"

Implementing the Custom Authorization Middleware

Define a middleware struct and implement the Transform trait for it. This trait allows you to modify requests and responses.

use actix_web::{dev::{Service, ServiceRequest, ServiceResponse, Transform}, Error};
use futures::future::{ok, Ready, LocalBoxFuture};
use std::task::{Context, Poll};
use std::rc::Rc;

pub struct AuthMiddleware;

impl Transform for AuthMiddleware
where
    S: Service, Error=Error>,
    S::Future: 'static,
{
    type Response = ServiceResponse;
    type Error = Error;
    type InitError = ();
    type Transform = AuthMiddlewareService;
    type Future = Ready>;

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

pub struct AuthMiddlewareService {
    service: Rc,
}

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

    fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll> {
        self.service.poll_ready(ctx)
    }

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let srv = Rc::clone(&self.service);
        Box::pin(async move {
            // Insert authorization logic here
            if let Some(auth_header) = req.headers().get("Authorization") {
                if auth_header.to_str().unwrap_or("").starts_with("Bearer ") {
                    return srv.call(req).await;
                }
            }
            let response = req.into_response(
                actix_web::HttpResponse::Unauthorized()
                    .body("Unauthorized")
                    .into_body(),
            );
            Ok(response)
        })
    }
}

Integrating Middleware into Your Application

Apply the middleware to your app by using the wrap method in your main server setup.

use actix_web::{HttpServer, App, web, Responder};

async fn index() -> impl Responder {
    "Hello, authorized user!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(AuthMiddleware)
            .route("/", web::get().to(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Testing Your Middleware

Use a tool like curl to test your server. Make requests with and without the Authorization header:

curl -H "Authorization: Bearer token123" http://127.0.0.1:8080/
curl http://127.0.0.1:8080/

The first request should succeed, returning "Hello, authorized user!", while the second should return "Unauthorized".

Conclusion

Building custom middleware in Actix Web provides a powerful way to enforce security policies tailored to your application's requirements. By intercepting requests early in the processing pipeline, you can ensure that only authorized users access protected resources. Experiment with extending this middleware to include more sophisticated authentication schemes or integrate with external identity providers.