Skip to main content

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

RouteMethodMiddlewareDescription
/refreshPOSTcookie_middlewareRefresh access token
/refresh-fullPOSTcookie_middlewareFull refresh validation
/logoutPOSTcookie_middlewareLogout current session
/userPOSTjwt_middlewareProtected 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.


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

ValidationDescription
Signature ValidationEnsures token integrity
Audience ValidationEnsures correct client usage
Expiration ValidationRejects expired tokens
Token Type ValidationEnsures 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

FieldDescription
issToken issuer
subUser identifier
rolesUser authorization roles
audIntended audience
expExpiration timestamp
iatIssued-at timestamp
nbfActivation timestamp
jtiUnique token identifier
typeToken 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

PropertyValue
TypeAccess Token
Expiration15 Minutes
Audiencepos-client
Issuerpos-system

Refresh Token Properties

PropertyValue
TypeRefresh Token
Expiration7 Days
Audiencepos-client
Issuerpos-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

ComponentResponsibility
RouterRegister authentication endpoints
Cookie MiddlewareValidate refresh token cookie
JWT MiddlewareValidate access token
JWT UtilityDecode and verify tokens
JWT ServiceGenerate access and refresh tokens
Claims StructureStore authentication payload
TokenResponseReturn generated token data