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
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
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.