← Week 3: Axum Web Framework

Day 21: Challenge — REST API with JWT Auth Middleware

Phase 2 · Jun 30, 2026

← Week 3: Axum Web Framework

Challenge Overview

Build a CRUD REST API in Axum with full JWT authentication middleware.

Requirements:

  1. POST /auth/login — accept {email, password}, return signed JWT
  2. GET /users, GET /users/:id, POST /users, PUT /users/:id, DELETE /users/:id
  3. All routes except /auth/login require a valid Authorization: Bearer <token>
  4. Claims extracted from JWT injected into request extensions
  5. In-memory user store (no DB required)
  6. Full test suite: happy path, unauthorized, not found, validation errors
← Week 3: Axum Web Framework

JWT Structure

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

#[derive(Serialize, Deserialize, Clone, Debug)]
struct Claims {
    sub: u64,       // user id
    email: String,
    exp: usize,     // Unix timestamp
}

fn sign_token(user: &User, secret: &[u8]) -> String {
    encode(
        &Header::default(),
        &Claims { sub: user.id, email: user.email.clone(), exp: expiry() },
        &EncodingKey::from_secret(secret),
    ).unwrap()
}
← Week 3: Axum Web Framework

Auth Middleware

async fn require_auth(
    State(state): State<AppState>,
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let token = req.headers()
        .get(AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.strip_prefix("Bearer "))
        .ok_or(StatusCode::UNAUTHORIZED)?;

    let claims = decode::<Claims>(token,
        &DecodingKey::from_secret(&state.jwt_secret),
        &Validation::default(),
    ).map_err(|_| StatusCode::UNAUTHORIZED)?.claims;

    req.extensions_mut().insert(claims);
    Ok(next.run(req).await)
}
← Week 3: Axum Web Framework

Router Setup

let app = Router::new()
    .route("/auth/login", post(login))
    .nest("/users",
        Router::new()
            .route("/", get(list_users).post(create_user))
            .route("/:id", get(get_user).put(update_user).delete(delete_user))
            .route_layer(middleware::from_fn_with_state(state.clone(), require_auth))
    )
    .with_state(state);
← Week 3: Axum Web Framework

Phase 2 Complete

Phase 2 Topic Core libraries
Week 1 Tokio runtime + I/O tokio, tokio-util
Week 2 Tower middleware tower, tower-http
Week 3 Axum HTTP framework axum, jsonwebtoken

You can now build:

  • High-throughput async TCP servers
  • Resilient HTTP clients with retry, timeout, circuit breaking
  • REST APIs with authentication, middleware, and structured error handling

Phase 3 starts tomorrow: Protocol Design — gRPC, custom binary protocols, and service meshes.