In modern web development, securing APIs and web applications is essential. JSON Web Tokens (JWT) provide a robust way to handle authentication and authorization. Axum, a powerful web framework for Rust, offers flexibility to implement JWT-based authentication seamlessly. This tutorial guides you through integrating Axum authentication with JWT tokens step-by-step.

Prerequisites

  • Basic knowledge of Rust programming language
  • Familiarity with Axum framework
  • Understanding of JWT concepts
  • Rust toolchain installed on your machine

Setting Up Your Rust Project

Create a new Rust project and add dependencies for Axum, JWT, and other required crates.

cargo new axum-jwt-auth
cd axum-jwt-auth

Edit Cargo.toml to include necessary dependencies:

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
jsonwebtoken = "8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Creating JWT Claims and Utility Functions

Define the structure of your JWT claims and utility functions to generate and verify tokens.

use serde::{Deserialize, Serialize};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey, TokenData, errors::Result};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    exp: usize,
}

const SECRET_KEY: &[u8] = b"your_secret_key";

fn create_jwt(username: &str) -> Result {
    let expiration = chrono::Utc::now()
        .checked_add_signed(chrono::Duration::hours(24))
        .expect("valid timestamp")
        .timestamp() as usize;

    let claims = Claims {
        sub: username.to_owned(),
        exp: expiration,
    };

    encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET_KEY))
}

fn verify_jwt(token: &str) -> Result> {
    decode::(token, &DecodingKey::from_secret(SECRET_KEY), &Validation::default())
}

Implementing Authentication Handlers

Create handlers for login, token validation, and protected routes.

use axum::{
    routing::{post, get},
    extract::{Json, TypedHeader},
    response::Json as ResponseJson,
    http::StatusCode,
    Router,
};
use axum::headers::Authorization;

#[derive(Deserialize)]
struct LoginRequest {
    username: String,
    password: String,
}

async fn login(Json(payload): Json) -> Result, StatusCode> {
    // Replace with real authentication logic
    if payload.username == "admin" && payload.password == "password" {
        match create_jwt(&payload.username) {
            Ok(token) => Ok(ResponseJson(token)),
            Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
        }
    } else {
        Err(StatusCode::Unauthorized)
    }
}

async fn protected(TypedHeader(auth): TypedHeader>) -> Result, StatusCode> {
    let token = auth.token().trim_start_matches("Bearer ").to_string();
    match verify_jwt(&token) {
        Ok(_) => Ok(ResponseJson("Access granted to protected route.")),
        Err(_) => Err(StatusCode::Unauthorized),
    }
}

Building the Axum Router

Set up your main function with routes for login and protected content.

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/login", post(login))
        .route("/protected", get(protected));

    println!("Server running on http://127.0.0.1:3000");
    axum::Server::bind(&"127.0.0.1:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

Testing Your JWT Authentication

Use tools like curl or Postman to test your API endpoints.

  • Send POST request to http://127.0.0.1:3000/login with JSON body:

{"username": "admin", "password": "password"}

  • Receive a JWT token in response.
  • Use this token in the Authorization header as Bearer <token> to access /protected.

If the token is valid, you will see an access granted message. Otherwise, you'll get an unauthorized error.

Conclusion

Integrating JWT authentication with Axum enhances your application's security. By following this tutorial, you can implement secure login flows and protected routes efficiently. Remember to keep your secret keys safe and consider implementing token refresh mechanisms for production applications.