Tower middleware to parse JWT tokens off the Authorization Bearer of requests and store the deserialized claims on the
request extension.
This is build on top of the jsonwebtoken crate and support all the algorithms supported by that crate.
Since this is a Tower middleware it can be used on any framework like Axum, Tonic, etc.
Symmetric example using Hyper
This example show how to use the re-exported DecodingKey and Validation from jsonwebtoken to parse and validate a JWT setup with a simple symmetric secret. It also show how to customize the default validator and how to extract both registered and public fields on a claim.
use chrono::{DateTime, Utc};
use http::{header::AUTHORIZATION, Request, Response, StatusCode};
use http_body_util::Empty;
use serde::Deserialize;
use std::convert::Infallible;
use tower::{Service, ServiceBuilder, ServiceExt};
use tower_jwt::{DecodingKey, JwtLayer, RequestClaim, Validation};
#[derive(Clone, Deserialize, Debug)]
struct Claim {
pub sub: String,
pub name: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub iat: DateTime<Utc>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn handle(req: Request<Empty<()>>) -> Result<Response<Empty<()>>, Infallible> {
let claim = req.extensions().get::<RequestClaim<Claim>>();
if let Some(claim) = claim {
assert_eq!(claim.claim.sub, "1234567890");
assert_eq!(claim.claim.name, "John Doe");
assert_eq!(
claim.claim.iat,
DateTime::parse_from_rfc3339("2018-01-18T01:30:00Z").unwrap()
);
Ok(Response::new(Empty::new()))
} else {
Ok(Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(Empty::new())
.unwrap())
}
}
let mut validation = Validation::default();
validation.validate_exp = false;
validation.required_spec_claims.clear();
let jwt_layer = JwtLayer::<Claim>::new(
validation,
DecodingKey::from_secret("symmetric secret".as_bytes()),
);
let mut service = ServiceBuilder::new().layer(jwt_layer).service_fn(handle);
let request = Request::builder().uri("/").body(Empty::new())?;
let status = service.ready().await?.call(request).await?.status();
assert_eq!(
status,
StatusCode::UNAUTHORIZED,
"request did not have a token while endpoint expected one"
);
let request = Request::builder()
.uri("/")
.header(AUTHORIZATION, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDAwfQ.CHiQ0VbodaR55aiN_0JJB7nWJBO__rt_7ur1WO-jZxg")
.body(Empty::new())?;
let status = service.ready().await?.call(request).await?.status();
assert_eq!(
status,
StatusCode::OK,
"request should extract the token correctly"
);
Ok(())
}
Assymmetric example using Axum
use axum::{routing::get, Extension, Router};
use http::{Request, StatusCode};
use http_body_util::Empty;
use ring::{
rand,
signature::{self, Ed25519KeyPair, KeyPair},
};
use serde::Deserialize;
use tower::ServiceExt;
use tower_jwt::{DecodingKey, JwtLayer, RequestClaim, Validation};
#[derive(Deserialize, Clone)]
pub struct Claim {
pub sub: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let doc = signature::Ed25519KeyPair::generate_pkcs8(&rand::SystemRandom::new()).unwrap();
let pair = Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
let public_key = pair.public_key().as_ref().to_vec();
let decoding_key = DecodingKey::from_ed_der(&public_key);
let mut validation = Validation::new(jsonwebtoken::Algorithm::EdDSA);
validation.set_issuer(&["test-issuer"]);
let router = Router::new()
.route(
"/",
get(|claim: Option<Extension<RequestClaim<Claim>>>| async move {
if let Some(Extension(claim)) = claim {
(StatusCode::OK, format!("Hello, {}", claim.claim.sub))
} else {
(StatusCode::UNAUTHORIZED, "Not authorized".to_string())
}
}),
)
.layer(JwtLayer::<Claim, _>::new(validation, move || {
let decoding_key = decoding_key.clone();
async {
decoding_key
}
}));
let response = router
.clone()
.oneshot(Request::builder().uri("/").body(Empty::new()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
Ok(())
}