Cookbook / Auth and sessions

JWT authentication

Issue tokens at login and protect a router so handlers only run for valid bearer tokens.

Jwt issues and verifies signed tokens. Its middleware::<Claims>() rejects requests without a valid Authorization: Bearer ... header with 401, and puts your claims type into the request for handlers to read.

src/main.rs
use axeon::auth::jwt::{now, Jwt};
use axeon::{Json, Router, Server, ServerError};
use serde::{Deserialize, Serialize};

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

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

fn main() {
    // Load at least 32 random bytes from your configuration in real life.
    let jwt = Jwt::hs256(b"load at least 32 random bytes from config!");

    let mut app = Server::new();
    app.state(jwt.clone());

    app.post("/login", |req| async move {
        let login: Login = req.json()?;
        // Check the credentials against your user store here.
        if login.password != "correct horse" {
            return Err(ServerError::Unauthorized("wrong username or password".into()));
        }
        let jwt = req.state::<Jwt>().unwrap();
        let claims = Claims { sub: login.username, exp: now() + 3600 };
        Ok(Json(jwt.issue(&claims)?))
    });

    let mut api = Router::new();
    api.middleware(jwt.middleware::<Claims>()); // 401 without a valid token
    api.get("/me", |req| async move {
        req.extension::<Claims>().map(|claims| claims.sub.clone())
    });
    app.mount("/api", api);

    app.listen("127.0.0.1:3000").expect("server failed to start");
}
Terminal
TOKEN=$(curl -s -X POST http://127.0.0.1:3000/login \
  -H 'content-type: application/json' \
  -d '{"username":"ada","password":"correct horse"}' | tr -d '"')

curl http://127.0.0.1:3000/api/me -H "authorization: Bearer $TOKEN"   # ada
curl -i http://127.0.0.1:3000/api/me                                  # 401

Notes#

  • Always include exp in your claims. Expired tokens are rejected.
  • Hash passwords with Argon2 or bcrypt, never compare them in plain text as this demo does.
  • For opaque tokens or API keys use auth::BearerAuth with your own async lookup, and auth::BasicAuth for internal tools.