Authorization using tower-http

In this example we will use tower-http's auth module to add a custom authorization layer. There are also two builtin bearer and password layers however since both bearer token and username and password combo are set at compile time, the use is somewhat limited.

In this case we implement the AuthorizeRequest trait for our custom Auth struct, which requires to set the appropriate ResponseBody (which in axum's case must be axum::body::BoxBody) as well as an authorize method. Here, based on the request and any data on the struct itself we can make authorization decisions. In this example we extract the Authorization header field and compare it with a pre-defined value. If it matches we can return Ok(()) and the route is taken otherwise we return an error response.

Dependencies

[dependencies]
axum = "0.5"
tower-http = { version = "0.2", features = ["auth"] }
tokio = { version = "1", features = ["full"] }

Code

use axum::body::BoxBody;
use axum::http::{header, Request, StatusCode};
use axum::response::Response;
use axum::routing::get;
use axum::{Router, Server};
use tower_http::auth::{AuthorizeRequest, RequireAuthorizationLayer};

#[derive(Clone)]
struct Auth {
    expected: String,
}

impl<B> AuthorizeRequest<B> for Auth {
    type ResponseBody = BoxBody;

    fn authorize(&mut self, request: &mut Request<B>) -> Result<(), Response<Self::ResponseBody>> {
        if let Some(header) = request.headers().get(header::AUTHORIZATION) {
            if header == &self.expected {
                return Ok(());
            }
        }

        let response = Response::builder()
            .status(StatusCode::UNAUTHORIZED)
            .body(BoxBody::default())
            .unwrap();

        Err(response)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = Router::new().route("/", get(|| async { "hello" })).layer(
        RequireAuthorizationLayer::custom(Auth {
            expected: "lol".to_string(),
        }),
    );

    Server::bind(&"0.0.0.0:8080".parse()?)
        .serve(app.into_make_service())
        .await?;

    Ok(())
}

Run

Start the server with

cargo run --bin auth-with-tower-http

Trying to get the root route without setting an authorization header results in a 401:

$ curl -i http://127.0.0.1:8080
HTTP/1.1 401 Unauthorized
content-length: 0
date: Tue, 08 Mar 2022 19:57:15 GMT

On the other hand, setting Authorization appropriately succeeds:

$ curl -i -H "Authorization: lol" http://127.0.0.1:8080
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 5
date: Tue, 08 Mar 2022 19:59:15 GMT

hello