Implementing Middleware in Rocket.rs

I am learning Rust by building projects and reading blogs. Also, I have listed references for this learning in the end. Since I already have experience in building with Go and JavaScript/TypeScript. I initially thought that middleware in Rocket.rs was the same as in other frameworks for Go and JavaScript/TypeScript.
But Rocket does not have traditional “middlewares,” and there are minimal resources for this topic in Rust on the Internet. I found only one blog related to this (linked in the references).
Instead, Rocket uses two related but distinct mechanisms:
Fairings
Fairing is a different approach used by rocket.rs to manage middlewares, which are for all the routes or can be said to be global middleware-like components that apply to all requests/responses, but not in the way we use middlewares in
Express.js
orGo(Echo/Gin)
.Fairings cannot conditionally skip certain requests—unlike guards.
Guards
While on the other hand, Guard (or Request Guards) are used at the time of request for validation and data extraction like from jwt token which we can use further in those specific-routes where we can apply these guards. They are similar to the middlewares in other frameworks in use case like validate header, tokens or request data. They can halt the request if certain conditions are not met during the request. Also, a request guard can only be created through its
FromRequest
implementation, and the type is notCopy
, the existence of a request guard value provides a type-level proof that the current request has been validated against an arbitrary policy. In Rust, Request Guard appears as the input to handlers.
Feature | Fairing | Request Guard |
Scope | Global—Runs on every request | Per-route runs only where you attach it. |
Purpose | Pre/Post processing for all requests/responses (logging, metric, CORS, etc.) | Validating and extracting request-specific data |
JWT Validation Fit? | It’s not impossible, but it is not efficient. Would have to run for every route, even public ones (you have to skip them manually) | ✅ Attach only to private routes; public routes won’t run it |
Can Block Request? | Not directly—can only modify request/response | ✅ Can reject unauthorized requests before they reach the handler |
The main reason I am using it Request Guards
in place of Fairings
is Fairings cannot respond to an incoming request directly
.
So, we will be using request guard for protecting routes by checking the JWT token.
Retrieve Header and token from the request
First, we have to retrieve the token from the request header
use rocket::request
let auth_header = request.headers().get_one("Authorization");
// as get_one returns Option<&str> which can be Some("header") or None
if auth_header.is_none() {
// no header
} else if auth_header == Some("") {
// Also it misses the case then auth_header = Some(" ") --> only spaces
// empty string
}
The problem in the above logic is that it will return an error when auth_header
is an empty string, if the header is missing, then auth_header
will be,None
and your later unwrap()
will panic.
let auth_header = match request.headers().get_one("Authorization") {
// extra check when it matches, extra check is only when it find Some(header)
// --> not None
Some(header) if !header.trim().is_empty() => header,
_ => return // error
};
This way, we can check for missing headers and white spaces in one place.
Now, we will extract the token from the header, as we know the header is in such a format.
Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUz
Our next task is to strip the token from the header
// Expected format of header: `Bearer token`
let token = match auth_header.strip_prefix("Bearer ") {
Some(t) if !t.trim().is_empty() => t.trim(),
_ => return Outcome::Error((Status::Unauthorized, ())),
};
// token is of &str type and contain jwt token
// match is used for case in which Authorization token is not Bearer Token
Further, we will get user_id
and role
from the token by decoding this JWT token with the help of JWT_SECRET
present in .env
.
Decoding the JWT token and get claims
// get JWT_SECRET from the .env
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set in .env");
// decode jwt
let result = decode::<Claims>(
token,
&DecodingKey::from_secret(jwt_secret.as_bytes()),
&Validation::new(Algorithm::HS256)
);
Sample success result
Ok(TokenData {
header: Header {
typ: Some("JWT"),
alg: HS256,
cty: None,
jku: None,
jwk: None,
kid: None,
x5u: None,
x5c: None,
x5t: None,
x5t_s256: None
},
claims: Claims {
user_id: "3ed6aa1c-a23a-4d9a-ae6f-b11defdc6ea7",
role: "ADMIN ",
exp: 1755255916
}
})
Now, we extract claims from this result in such way:
let claims = match result {
Ok(data) => data.claims,
Err(_) => return Outcome::Error((Status::Unauthorized, ())),
};
and with this we have extracted the claims from the token.
Combine All the code to use in a Request Guard
Here is the complete combines code for the auth_guard.rs
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome};
use rocket::Request;
use rocket::serde::{Serialize, Deserialize};
use rocket::async_trait;
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
// Data stored in the JWT
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub user_id: String,
pub role: String,
pub exp: usize, // expiration timestamp of JWT token
}
// Guard struct
pub struct AuthenticatedUser(pub Claims);
#[async_trait]
impl<'r> FromRequest<'r> for AuthenticatedUser {
type Error = ();
async fn from_request(
request: &'r Request<'_>,
) -> Outcome<Self, Self::Error> {
// extract header
let auth_header = match request.headers().get_one("Authorization") {
Some(header) if !header.trim().is_empty() => header,
_ => return Outcome::Error((Status::Unauthorized, ()))
};
// extract token, header must start with bearer
// Expected format of header: `Bearer token`
let token = match auth_header.strip_prefix("Bearer ") {
Some(t) if !t.trim().is_empty() => t.trim(),
_ => return Outcome::Error((Status::Unauthorized, ())),
};
// get jwt secret
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set in .env");
// decode jwt
let result = decode::<Claims>(
token,
&DecodingKey::from_secret(jwt_secret.as_bytes()),
&Validation::new(Algorithm::HS256)
);
let claims = match result {
Ok(data) => data.claims,
Err(_) => return Outcome::Error((Status::Unauthorized, ())),
};
Outcome::Success(AuthenticatedUser(claims))
}
}
Usage of the Request Guard
// So on this is your authenticated route using request guard
use crate::models::user::UserLogin;
use crate::guards::auth_guard::AuthenticatedUser;
use rocket::http::{Status};
use rocket::serde::json::{Json, json, Value};
#[get("/me")]
pub fn user_details(user: AuthenticatedUser) -> Result<Json<Value>, Status> {
Ok(Json(json!({
"success": true,
"user_id": user.0.user_id,
"role": user.0.role.trim() // trim white-spaces for cases like role = "ADMIN "
})))
}
// as pub struct AuthenticatedUser(pub Claims); is a tuple struct with only 1 value
// that's why we use 0 for its indexing
Related Articles
- JWT Auth in Rocket.rs with Argon2 password hashing and cookie storing in Rust- https://nishujangra27.hashnode.dev/implementing-jwt-authentication-in-rocketrs
Reference
Request Guards – Rocket.rs Guide – Official Rocket documentation on creating and using Request Guards for request data extraction and validation.
Fairings – Rocket.rs Guide – Overview of Rocket’s Fairing system for request/response modification and application lifecycle hooks.
Implementing Middleware in Rust (Shuttle) – Blog post demonstrating middleware patterns, including Rocket-specific examples.
Triggering
Outcome::Failure
in Rocket Request Guards – Stack Overflow – Community discussion on when and how to returnOutcome::Failure
in custom guards.Retrieving HTTP Headers in Rocket – Stack Overflow – Techniques for accessing header values from incoming requests.
Encoding & Decoding JWT in Rust – Medium – Tutorial on working with JWT tokens in Rust using the
jsonwebtoken
crate.
Subscribe to my newsletter
Read articles from Nishant directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
