rust
66 lines · 8 steps
How a JWT extractor works in Axum
Turning a Claims struct into an Axum extractor that validates a bearer token from the request headers.
Explained by
highlit
1use axum::{
2 extract::{FromRef, FromRequestParts},
3 http::{header, request::Parts, StatusCode},
4 RequestPartsExt,
5};
6use axum_extra::{
7 headers::{authorization::Bearer, Authorization},
8 TypedHeader,
9};
10use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone)]
14pub struct JwtConfig {
15 pub decoding_key: DecodingKey,
16 pub validation: Validation,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct Claims {
21 pub sub: String,
22 pub email: String,
23 pub roles: Vec<String>,
24 pub exp: usize,
25}
26
27pub enum AuthError {
28 MissingToken,
29 InvalidToken,
30}
31
32impl axum::response::IntoResponse for AuthError {
33 fn into_response(self) -> axum::response::Response {
34 let (status, msg) = match self {
35 AuthError::MissingToken => (StatusCode::UNAUTHORIZED, "missing bearer token"),
36 AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "invalid or expired token"),
37 };
38 (status, [(header::WWW_AUTHENTICATE, "Bearer")], msg).into_response()
39 }
40}
41
42impl<S> FromRequestParts<S> for Claims
43where
44 JwtConfig: FromRef<S>,
45 S: Send + Sync,
46{
47 type Rejection = AuthError;
48
49 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
50 let TypedHeader(Authorization(bearer)) = parts
51 .extract::<TypedHeader<Authorization<Bearer>>>()
52 .await
53 .map_err(|_| AuthError::MissingToken)?;
54
55 let config = JwtConfig::from_ref(state);
56
57 let token_data = decode::<Claims>(
58 bearer.token(),
59 &config.decoding_key,
60 &config.validation,
61 )
62 .map_err(|_| AuthError::InvalidToken)?;
63
64 Ok(token_data.claims)
65 }
66}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Implementing FromRequestParts lets any type become a handler argument that runs custom logic before the handler.
- 2FromRef decouples the extractor from the exact application state shape, requiring only that JwtConfig be extractable from it.
- 3Modeling failures as an IntoResponse enum keeps auth errors type-safe while still producing proper HTTP responses.
Related explainers
go
package server import ( "net/http"
Rate limiting HTTP handlers with a token bucket
rate-limiting
token-bucket
middleware
Advanced
7 steps
rust
use std::collections::HashMap; #[derive(Clone, Copy, PartialEq)] enum Color {
Detecting cycles with three-color DFS in Rust
graph-algorithms
cycle-detection
depth-first-search
Intermediate
9 steps
go
package middleware import ( "fmt"
How a panic-recovery middleware works in Gin
middleware
panic-recovery
error-reporting
Intermediate
8 steps
rust
#[derive(Deserialize)] pub struct CreateArticle { title: String, body: String,
Building a create endpoint in Axum
extractors
json-deserialization
sqlx
Intermediate
7 steps
go
func UploadDocument(c *gin.Context) { fileHeader, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
Handling multipart uploads in Gin
multipart-upload
validation
error-handling
Intermediate
9 steps
rust
use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use axum::body::Body;
A maintenance-mode gate in Axum
middleware
shared-state
atomics
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-a-jwt-extractor-works-in-axum-explained-rust-6314/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.