Expand description
Lightweight authorization middleware for axum,
following JSON Object Signing and Encryption (JOSE) standards.
§Overview
This crate provides a tower-based AuthorizationLayer that integrates with axum’s
middleware system to add JWT-based authorization to your application. The middleware validates JWTs from incoming
requests against JWK (JSON Web Key) sets, which can be either provided locally or fetched from remote identity providers.
§Quickstart
This example illustrates how to validate JWTs against a remote JWK set provided e.g. by your OpenID Connect provider.
It also shows how to enable caching using the RemoteJwkSetBuilder::with_cache method to avoid fetching the JWK
set on every request. Choose a TTL that balances responsiveness to key rotation with provider
load. Shorter TTLs react faster to key rotation, while longer TTLs reduce requests to your identity provider.
Note though, that the cache is not only invalidated when reaching its TTL but also when a JWT with an unknown kid
(key ID) is encountered. Therefore, in addition to caching, consider configuring rate limiting using
RemoteJwkSetBuilder::with_rate_limit to prevent running into your identity provider’s server-side rate limits.
use axum::{routing::get, Router};
use axum_jose::{AuthorizationLayer, RemoteJwkSet};
use std::num::NonZero;
use std::time::Duration;
use url::Url;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Configure a `RemoteJwkSet` to fetch the JWK set e.g. from your OpenID
// Connect provider which typically exposes JWK sets at a
// `.well-known/jwks.json` endpoint
let remote_jwk_set = RemoteJwkSet::builder(Url::parse(
"https://your.oidc.provider/.well-known/jwks.json",
)?)
// Use caching to avoid fetching the JWK set on every request
.with_cache(Duration::from_secs(300))
// Configure rate limiting to avoid hitting provider limits
.with_rate_limit(NonZero::new(10).unwrap(), Duration::from_secs(60))
.build();
// Create an authorization layer using the remote JWK set to validate JWTs.
let authorization_layer = AuthorizationLayer::with_remote_jwk_set(
remote_jwk_set,
Url::parse("https://your.jwt.issuer")?,
"your.jwt.audience".to_string(),
);
// Protect your `axum` routes with the authorization layer.
let router = Router::new()
.route("/protected", get(|| async { "Hello, authorized user!" }))
.layer(authorization_layer);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, router).await?;
Ok(())
}§Accessing JWT Claims
After successful authorization, the JWT claims are available via axum’s request extensions:
use axum::{Extension, routing::get};
use axum_jose::authorization::Claims;
use serde_json::Value;
async fn protected_handler(Extension(Claims(claims)): Extension<Claims>) -> String {
// Access standard claims
let subject = claims.get("sub").and_then(|v| v.as_str()).unwrap_or("unknown");
// Access custom claims
let role = claims.get("role").and_then(|v| v.as_str()).unwrap_or("user");
format!("Hello, {}! Your role is: {}", subject, role)
}For type-safe claim extraction, deserialize into a custom struct:
use serde::Deserialize;
use axum_jose::authorization::Claims;
use axum::Extension;
#[derive(Deserialize)]
struct MyClaims {
sub: String,
role: String,
}
async fn protected_handler(Extension(Claims(claims)): Extension<Claims>) -> String {
match serde_json::from_value::<MyClaims>(claims) {
Ok(my_claims) => format!("Hello, {}! Your role is: {}", my_claims.sub, my_claims.role),
Err(_) => "Invalid claims".to_string(),
}
}§Custom HTTP Client
Provide a custom reqwest::Client for specific requirements like timeouts, proxies, etc.:
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.build()?;
let jwk_set = RemoteJwkSet::builder(
Url::parse("https://example.com/.well-known/jwks.json")?
)
.with_http_client(http_client)
.build();§Using a Local JWK Set
For testing purposes or scenarios where you manage keys locally you can use a jsonwebtoken::jwk::JwkSet directly
instead of fetching one from a remote URL:
use axum_jose::AuthorizationLayer;
use jsonwebtoken::jwk::JwkSet;
// Load or construct your JWK set
let jwk_set: JwkSet = serde_json::from_str(r#"{"keys": [...]}"#)?;
let auth_layer = AuthorizationLayer::with_local_jwk_set(
jwk_set,
Url::parse("https://your.jwt.issuer")?,
"your.jwt.audience".to_string(),
);
let router = Router::new()
.route("/protected", get(|| async { "Authorized!" }))
.layer(auth_layer);
§Error Handling
When authorization fails, the middleware returns an Error instance that translates into a 401 Unauthorized
response with a JSON body containing a description of what went wrong:
{
"error": "JWT validation failed"
}See Error for more details on possible error cases.
Re-exports§
pub use authorization::AuthorizationLayer;
Modules§
Structs§
- Remote
JwkSet - Remote
JwkSet Builder - Builder for configuring a
RemoteJwkSetwith optional caching and rate limiting.