Simple API Key Authentication in Axum: Step-by-Step Guide

When building web applications, authentication ensures that only authorized users can access certain resources. In this guide, we will create a simple authentication mechanism using an API key in Axum.

We’ll implement middleware to check the Authorization header for an API key. If the key is valid, we’ll display a success message; otherwise, we’ll return an Unauthorized response.

Let’s dive in!

1. Step-by-Step Code Build

We will start with a minimal Axum setup and progressively add the authentication middleware.

Step 1: Set Up the Basic Axum Project

Create a new Axum project:

cargo new axum_auth_example --bin
cd axum_auth_example

Then, add the necessary dependencies to Cargo.toml:

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }

Now, let’s create a basic "Hello, World!" Axum application.

use axum::{
    routing::get,
    Router,
};

#[tokio::main]
async fn main() {
    // build our application with a single route
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

This basic app listens on port 3000 and will display "Hello, World!" at http://localhost:3000.

Step 2: Adding Authentication Middleware

Now let’s create the middleware that checks for the presence of a valid API key in the Authorization header.

use axum::{
    Router,
    http::StatusCode,
    routing::get,
    response::{IntoResponse, Response},
    middleware::{self, Next},
    extract::Request,
};

const VALID_API_KEY: &str = "Bearer my_secret_api_key";

async fn auth(req: Request, next: Next) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get(axum::http::header::AUTHORIZATION)
        .and_then(|header| header.to_str().ok());

    let auth_header = if let Some(auth_header) = auth_header {
        auth_header
    } else {
        return Err(StatusCode::UNAUTHORIZED);
    };

    if auth_header == VALID_API_KEY {
        // If the API key matches, proceed to the next handler
        Ok(next.run(req).await)
    } else {
        // Otherwise, return Unauthorized
        Err(StatusCode::UNAUTHORIZED)
    }
}

async fn handler() -> impl IntoResponse {
    "You are authenticated!"
}

Here’s what we’re doing in the code:

  • API Key Validation: We check the Authorization header for the string Bearer my_secret_api_key. If it matches, the request is authenticated.
  • Return Message: If the API key is correct, we allow the request to continue to the handler, which simply responds with "You are authenticated!". If the key is incorrect or missing, we return a 401 Unauthorized.

Step 3: Apply Middleware to the Routes

Finally, we apply this middleware to our route using route_layer to ensure every request to the root path is authenticated:

let app = Router::new()
    .route("/", get(handler))
    .route_layer(middleware::from_fn(auth));

This ensures that every request is checked for the valid API key before it reaches the handler.

2. Concepts and Explanations

Let’s break down the important parts of this example:

  • Middleware in Axum: Middleware in Axum is used to modify the request or response. In our case, we use middleware to check if the request includes a valid API key in the Authorization header. If the key matches the expected value, the request continues; otherwise, we reject it with 401 Unauthorized.

  • Authorization Header: The Authorization header in HTTP requests is commonly used to send credentials. In our example, we expect the header to contain an API key, Bearer my_secret_api_key.

  • Route Layering: We used route_layer(middleware::from_fn(auth)) to apply the auth middleware to the route. This ensures that all requests to the root ("/") pass through our authentication check.

3. Challenges

Here’s a challenge for you:

  • Challenge: Modify the middleware to accept different API keys or use a more complex key validation (e.g., checking keys from an environment variable or a database). How would you handle storing and rotating keys securely?

Try this challenge to expand your authentication system.

4. Recap and Conclusion

In this tutorial, we’ve created a simple API key authentication middleware in Axum. We started by building a basic Axum app, then added middleware to check the Authorization header for a valid API key. If the key is valid, we show a success message; otherwise, we respond with 401 Unauthorized.

This approach is easy to understand and can be extended to more complex authentication strategies as needed.

Next, you could explore more advanced authentication methods such as OAuth or JWT tokens. Thanks for following along—now you’re ready to secure your Axum applications!