rust 145 lines · 3 tabs

Axum Bearer Token Extractor with Shared Auth State and Typed Claims

Shared by codesnips Aug 2026
3 tabs
use axum::{
    extract::{FromRequestParts, State},
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;

use crate::state::{verify_token, AppState};

#[derive(Debug, Clone)]
pub struct AuthUser {
    pub id: String,
    pub scopes: Vec<String>,
}

#[derive(Debug)]
pub enum AuthError {
    MissingToken,
    InvalidToken,
    Expired,
}

impl IntoResponse for AuthError {
    fn into_response(self) -> Response {
        let msg = match self {
            AuthError::MissingToken => "missing bearer token",
            AuthError::InvalidToken => "invalid bearer token",
            AuthError::Expired => "token expired",
        };
        (StatusCode::UNAUTHORIZED, Json(json!({ "error": msg }))).into_response()
    }
}

impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let header = parts
            .headers
            .get(axum::http::header::AUTHORIZATION)
            .and_then(|v| v.to_str().ok())
            .ok_or(AuthError::MissingToken)?;

        let token = header
            .strip_prefix("Bearer ")
            .ok_or(AuthError::InvalidToken)?
            .trim();

        let State(app): State<AppState> = parts
            .extract::<State<AppState>>()
            .await
            .map_err(|_| AuthError::InvalidToken)?;

        verify_token(&app, token)
    }
}
3 files · rust Explain with highlit

This snippet shows how token authentication is wired into axum as a custom extractor rather than as ad-hoc code inside every handler. The AuthUser extractor tab defines a lightweight AuthUser type that any handler can request in its argument list; when it appears, axum runs the extractor before the handler body, so a route only compiles with an authenticated user in scope. This is the idiomatic way to enforce cross-cutting concerns in axum: the type system makes authentication a precondition rather than a convention.

The extractor is implemented via FromRequestParts because a bearer token lives in the request headers and never needs the body, which keeps the extractor cheap and lets it compose with body extractors like Json in the same handler signature. from_request_parts pulls the Authorization header, strips the Bearer prefix, and rejects malformed input early. Note the use of parts.extract::<State<AppState>>() to reach shared application state without threading it through the function signature manually.

The AuthError type centralizes failure modes and implements IntoResponse, so returning an error from the extractor produces a real HTTP status and JSON body. This matters because an extractor's Rejection type is what the framework renders when authentication fails; mapping MissingToken, InvalidToken, and Expired to distinct 401 responses gives clients actionable feedback while never leaking internal detail.

The AppState and verify tab holds the AppState with a decoding key and the verify_token function that does the real cryptographic work using jsonwebtoken. Validation is configured up front with an expected audience and leeway, and the decoded Claims are converted into an AuthUser. Keeping verification in state means the key is loaded once at startup, not per request.

The router wiring tab shows the payoff: protected_route simply names AuthUser as a parameter and receives a verified user, while AppState is shared through with_state. The trade-off is that every protected handler pays a small verification cost per call; for higher throughput a caching layer or a tower middleware layer applied to a route group would be preferable. This pattern shines when different routes need different guarantees, since extractors compose per handler.


Related snips

ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
bash
#!/usr/bin/env bash
set -euo pipefail

export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"

Secrets management with environment isolation and Vault

secrets-management vault environment-variables
by Kai Nakamura 1 tab
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Axum Bearer Token Extractor with Shared Auth State and Typed Claims — share card
Link copied