JWT Authentication Architecture
This documentation explains the JWT authentication implementation using:
- Axum Router
- JWT Middleware
- Access Token Validation
- Refresh Token Validation
- Token Generation Service
Project Structure
Authentication Flow Overview
Route Configuration
File Structure
Source Code
pub fn create_routes(
state: AppState,
) -> Router<AppState> {
Router::new()
.route("/refresh", post(refresh))
.route("/refresh-full", post(refresh_full))
.route("/logout", post(logout))
.layer(ServiceBuilder::new().layer(middleware::from_fn(cookie_middleware)))
Router::new()
.route("/user", post(user))
.layer(ServiceBuilder::new().layer(middleware::from_fn(jwt_middleware)))
}
Route Explanation
| Route | Method | Middleware | Description |
|---|---|---|---|
/refresh | POST | cookie_middleware | Refresh access token |
/refresh-full | POST | cookie_middleware | Full refresh validation |
/logout | POST | cookie_middleware | Logout current session |
/user | POST | jwt_middleware | Protected user endpoint |
Middleware Layer Design
JWT Middleware
File Structure
Cookie Middleware
Source Code
pub async fn cookie_middleware(
jar: CookieJar,
req: Request,
next: Next
)->Result<Response,AppError> {
let cookie = jar.get("refresh-token");
if cookie.is_none() {
return Err(
AppError::Unauthorized(
0,
"xxxx".to_string(),
"xxxx".to_string()
)
)
}
Ok(next.run(req).await)
}
Cookie Middleware Explanation
Purpose
Validates the existence of the refresh token cookie.
Cookie Extraction
let cookie = jar.get("refresh-token");
Retrieves the refresh-token cookie from the incoming request.
Validation Logic
if cookie.is_none()
Checks whether the refresh token exists.
If missing:
- Request is rejected
- Unauthorized response is returned
Continue Middleware Chain
Ok(next.run(req).await)
Passes request execution to the next middleware or handler.
JWT Middleware
Source Code
pub async fn jwt_middleware(
mut req: Request,
next: Next,
)->Result<Response,AppError>{
let auth_header = req
.headers()
.get(header::AUTHORIZATION)
.and_then(|h| h.to_str().ok());
let auth_header = match auth_header {
Some(v)=>v,
None => {
return Err(AppError::UnauthorizedAuth(41i8,"Authorization".to_string(),"xxxx".to_string()));
}
};
let token = match auth_header.strip_prefix("Bearer ") {
Some(v)=>v,
None => {
return Err(AppError::UnauthorizedAuth(41i8,"Authorization".to_string(),"xxxx".to_string()));
}
};
let claims = match verify_access_token(&token) {
Ok(v)=>v,
Err(e) => {
return Err(AppError::from(e));
}
};
req.extensions_mut().insert(claims);
Ok(next.run(req).await)
}
JWT Middleware Flow
Authorization Header Format
Expected request header:
Authorization: Bearer xxxx
Request Extension Injection
req.extensions_mut().insert(claims);
Stores validated JWT claims inside request extensions.
This allows route handlers to access authenticated user information.
JWT Utility Module
File Structure
Access Token Verification
Source Code
pub fn verify_access_token(
token:&str,
) -> Result<Claims,AppError>{
let mut validation = Validation::default();
validation.set_audience(&["pos-client"]);
let decoded = decode::<Claims>(
token,
&DecodingKey::from_secret(
std::env::var("JWT_SECRET").unwrap().as_bytes(),
),
&validation,
).map_err(|e|{
match *e.kind() {
ErrorKind::ExpiredSignature =>{
AppError::UnauthorizedAuth(41i8,"Authorization".to_string(),"xxxx".to_string())
}
ErrorKind::InvalidToken =>{
AppError::UnauthorizedAuth(41i8,"Authorization".to_string(),"xxxx".to_string())
}
ErrorKind::InvalidSignature =>{
AppError::UnauthorizedAuth(41i8,"Authorization".to_string(),"xxxx".to_string())
}
ErrorKind::InvalidAudience => {
AppError::UnauthorizedAuth(
41i8,"Authorization".to_string(),"xxxx".to_string()
)
}
_=>{
AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string())
}
}
})?;
let claims = decoded.claims;
if claims.r#type !="access-token" {
return Err(AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string()));
}
Ok(claims)
}
Access Token Validation Process
Validation Rules
| Validation | Description |
|---|---|
| Signature Validation | Ensures token integrity |
| Audience Validation | Ensures correct client usage |
| Expiration Validation | Rejects expired tokens |
| Token Type Validation | Ensures access-token usage |
Refresh Token Verification
Source Code
pub fn verify_refresh_token(
token:&str,
)->Result<RefreshClaims,AppError> {
let mut validation = Validation::default();
validation.set_audience(&["pos-client"]);
let decoded = decode::<RefreshClaims>(
token,
&DecodingKey::from_secret(
std::env::var("JWT_SECRET").unwrap().as_bytes(),
),
&validation,
).map_err(|e|{
match *e.kind() {
ErrorKind::ExpiredSignature =>{
AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string())
}
ErrorKind::InvalidToken =>{
AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string())
}
ErrorKind::InvalidSignature =>{
AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string())
}
ErrorKind::InvalidAudience => {
AppError::UnauthorizedAuth(
0i8,"Authorization".to_string(),"xxxx".to_string()
)
}
_=>{
AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string())
}
}
})?;
let claims = decoded.claims;
if claims.r#type !="refresh-token" {
return Err(AppError::UnauthorizedAuth(0i8,"Authorization".to_string(),"xxxx".to_string()));
}
Ok(claims)
}
Claims Structure
Access Claims
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims{
pub iss:String,
pub sub:String,
pub roles:String,
pub aud:String,
pub exp:usize,
pub iat:usize,
pub nbf:usize,
pub jti:String,
#[serde(rename = "type")]
pub r#type:String,
}
Refresh Claims
#[derive(Debug,Serialize,Deserialize)]
pub struct RefreshClaims{
pub iss:String,
pub sub:String,
pub aud:String,
pub exp:usize,
pub iat:usize,
pub nbf:usize,
pub roles:String,
pub jti:String,
#[serde(rename = "type")]
pub r#type:String,
}
Claims Field Description
| Field | Description |
|---|---|
iss | Token issuer |
sub | User identifier |
roles | User authorization roles |
aud | Intended audience |
exp | Expiration timestamp |
iat | Issued-at timestamp |
nbf | Activation timestamp |
jti | Unique token identifier |
type | Token category |
Token Response Structure
pub struct TokenResponse {
pub token: String,
pub jti: Uuid,
pub user_id: String,
pub expires_in:chrono::NaiveDateTime,
}
JWT Service
File Structure
Access Token Generation
Source Code
pub fn generate_access_token(
user_id:&str,
roles:&str,
)->Result<String,String>{
let now = Utc::now();
let expiration = now + Duration::minutes(15);
let ts = Timestamp::now(NoContext);
let jti = Uuid::new_v7(ts);
let user_id =user_id.to_string();
let claims = Claims{
iss:"pos-system".to_string(),
sub:user_id.clone(),
roles:roles.to_string(),
aud:"pos-client".to_string(),
exp:expiration.timestamp() as usize,
iat: now.timestamp() as usize,
nbf:now.timestamp() as usize,
jti:jti.clone().to_string(),
r#type:"access-token".to_string(),
};
let token = encode_jwt(&claims)?;
Ok(token)
}
Refresh Token Generation
Source Code
pub fn generate_refresh_token(
user_id:&str,
roles:&str,
)->Result<TokenResponse,String>{
let now = Utc::now();
let expiration = now + Duration::days(7);
let ts = Timestamp::now(NoContext);
let jti = Uuid::new_v7(ts);
let claims = RefreshClaims{
iss:"pos-system".to_string(),
sub:user_id.to_string(),
aud:"pos-client".to_string(),
exp:expiration.timestamp() as usize,
roles:roles.to_string(),
iat: now.timestamp() as usize,
nbf:now.timestamp() as usize,
jti:jti.clone().to_string(),
r#type:"refresh-token".to_string(),
};
let token = encode_jwt(&claims)?;
Ok(TokenResponse {
token,
jti,
user_id:user_id.to_string(),
expires_in:expiration.naive_utc(),
})
}
Token Generation Lifecycle
Access Token Properties
| Property | Value |
|---|---|
| Type | Access Token |
| Expiration | 15 Minutes |
| Audience | pos-client |
| Issuer | pos-system |
Refresh Token Properties
| Property | Value |
|---|---|
| Type | Refresh Token |
| Expiration | 7 Days |
| Audience | pos-client |
| Issuer | pos-system |
Security Design
Access Token
Short-lived token used for API authorization.
Refresh Token
Long-lived token used for session continuation.
JWT Secret
Used for:
- Token signature generation
- Signature verification
- Token integrity validation
Authentication Architecture Summary
| Component | Responsibility |
|---|---|
| Router | Register authentication endpoints |
| Cookie Middleware | Validate refresh token cookie |
| JWT Middleware | Validate access token |
| JWT Utility | Decode and verify tokens |
| JWT Service | Generate access and refresh tokens |
| Claims Structure | Store authentication payload |
| TokenResponse | Return generated token data |