jsonwebtoken (Rust)
Authentication Library
jsonwebtoken (Rust)
Overview
jsonwebtoken is a high-performance authentication library for generating and verifying JSON Web Tokens (JWT) in the Rust programming language. As of 2025, it stands as the most popular JWT implementation on crates.io and represents the standard choice for authentication in the Rust ecosystem. Supporting multiple cryptographic algorithms including HS256, RS256, and ES256, it leverages Rust's zero-cost abstractions to deliver exceptionally fast token processing. The library integrates seamlessly with major web frameworks like axum, actix-web, and warp.
Details
The jsonwebtoken crate is a comprehensive JWT implementation for Rust developed by Keats. It provides full RFC 7519 compliant JWT specification support with automated claim validation (exp, nbf, iss, aud, sub), flexible custom claim handling, and advanced security features.
Version 9.x as of 2025 introduces several important characteristics:
Technical Features
- Zero-Cost Abstractions: Safe and fast implementation leveraging Rust's ownership system
- Type Safety: Compile-time type checking ensures safe claim processing
- Memory Safety: Complete avoidance of buffer overflows and memory leaks
- Async Support: Full compatibility with Future/async traits
Cryptographic Algorithm Support
- HMAC: HS256, HS384, HS512 (shared secret-based)
- RSA: RS256, RS384, RS512 (public key cryptography)
- ECDSA: ES256, ES384 (elliptic curve digital signature)
- EdDSA: Ed25519 (Edwards curve digital signature)
Security Features
- Automatic Claim Validation: Automatic verification of exp (expiration) and nbf (not before)
- Custom Validation: Configurable validation for iss (issuer), aud (audience), and sub (subject)
- Required Claims: Validation settings to enforce specific claim presence
- Signature Verification Bypass: insecure_disable_signature_validation for debugging purposes
Advantages and Disadvantages
Advantages
- High Performance: Extremely fast processing through Rust's zero-cost abstractions
- Memory Safety: Complete memory safety through Rust's ownership system
- Type Safety: Runtime error avoidance through compile-time type checking
- Rich Algorithm Support: Comprehensive support for modern cryptographic standards
- Lightweight: Minimal dependencies resulting in reduced binary size
- Mature Library: Extensive track record and reliability in the Rust ecosystem
- Async Ready: Full compatibility with async/await patterns
Disadvantages
- Rust Only: Limited interoperability with other programming languages
- Learning Curve: Requires understanding of both Rust language and JWT specifications
- Ecosystem: Limited third-party libraries compared to Java or Python
- Key Format: Only supports PKCS8 format for EC private keys
- Debug Complexity: Initial learning difficulty due to Rust's borrow checker
Reference Pages
- GitHub Repository
- crates.io Package
- docs.rs Documentation
- JWT.io Rust Section
- Rust JWT Authentication Tutorial
Usage Examples
Installation and Basic Configuration
# Cargo.toml
[dependencies]
jsonwebtoken = "9"
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1.0", features = ["full"] }
# Web framework integration (optional)
axum = "0.7"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors"] }
// src/lib.rs - Basic imports and configuration
use jsonwebtoken::{
encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey,
errors::Result as JwtResult,
};
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc, Duration};
use std::env;
// JWT configuration structure
pub struct JwtConfig {
pub secret_key: String,
pub algorithm: Algorithm,
pub expiration_hours: i64,
pub issuer: String,
pub audience: String,
}
impl Default for JwtConfig {
fn default() -> Self {
Self {
secret_key: env::var("JWT_SECRET").unwrap_or_else(|_| {
"your-super-secret-key-change-this-in-production".to_string()
}),
algorithm: Algorithm::HS256,
expiration_hours: 24,
issuer: "rust-jwt-example".to_string(),
audience: "rust-jwt-users".to_string(),
}
}
}
// JWT claims structure
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String, // Subject (user ID)
pub iss: String, // Issuer
pub aud: String, // Audience
pub exp: i64, // Expiration time (Unix timestamp)
pub iat: i64, // Issued at (Unix timestamp)
pub nbf: i64, // Not before (Unix timestamp)
pub jti: String, // JWT ID
// Custom claims
pub user_id: u64,
pub username: String,
pub email: String,
pub roles: Vec<String>,
pub permissions: Vec<String>,
}
impl Claims {
pub fn new(
user_id: u64,
username: String,
email: String,
roles: Vec<String>,
permissions: Vec<String>,
config: &JwtConfig,
) -> Self {
let now = Utc::now();
let exp_time = now + Duration::hours(config.expiration_hours);
Self {
sub: user_id.to_string(),
iss: config.issuer.clone(),
aud: config.audience.clone(),
exp: exp_time.timestamp(),
iat: now.timestamp(),
nbf: now.timestamp(),
jti: uuid::Uuid::new_v4().to_string(),
user_id,
username,
email,
roles,
permissions,
}
}
}
Token Generation and Verification
// src/jwt.rs - JWT service implementation
use crate::{Claims, JwtConfig};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use std::collections::HashSet;
pub struct JwtService {
config: JwtConfig,
encoding_key: EncodingKey,
decoding_key: DecodingKey,
validation: Validation,
}
impl JwtService {
pub fn new(config: JwtConfig) -> Self {
let encoding_key = EncodingKey::from_secret(config.secret_key.as_bytes());
let decoding_key = DecodingKey::from_secret(config.secret_key.as_bytes());
let mut validation = Validation::new(config.algorithm);
validation.set_issuer(&[&config.issuer]);
validation.set_audience(&[&config.audience]);
validation.validate_exp = true;
validation.validate_nbf = true;
// Set required claims
let mut required_claims = HashSet::new();
required_claims.insert("sub".to_string());
required_claims.insert("user_id".to_string());
required_claims.insert("username".to_string());
required_claims.insert("email".to_string());
validation.required_spec_claims = required_claims;
Self {
config,
encoding_key,
decoding_key,
validation,
}
}
// Generate access token
pub fn generate_access_token(&self, claims: &Claims) -> JwtResult<String> {
let header = Header::new(self.config.algorithm);
encode(&header, claims, &self.encoding_key)
}
// Verify and parse token
pub fn verify_token(&self, token: &str) -> JwtResult<Claims> {
let token_data = decode::<Claims>(token, &self.decoding_key, &self.validation)?;
Ok(token_data.claims)
}
// Generate refresh token (long-term validity)
pub fn generate_refresh_token(&self, user_id: u64) -> JwtResult<String> {
let now = Utc::now();
let exp_time = now + Duration::days(30); // Valid for 30 days
let refresh_claims = Claims {
sub: user_id.to_string(),
iss: self.config.issuer.clone(),
aud: format!("{}-refresh", self.config.audience),
exp: exp_time.timestamp(),
iat: now.timestamp(),
nbf: now.timestamp(),
jti: uuid::Uuid::new_v4().to_string(),
user_id,
username: "refresh".to_string(), // Minimal info for refresh tokens
email: "".to_string(),
roles: vec![],
permissions: vec![],
};
let header = Header::new(self.config.algorithm);
encode(&header, &refresh_claims, &self.encoding_key)
}
// Extract user ID from token without verification (for logging, etc.)
pub fn extract_user_id_without_verification(&self, token: &str) -> Option<u64> {
let mut validation = Validation::new(self.config.algorithm);
validation.insecure_disable_signature_validation();
validation.validate_exp = false;
validation.validate_nbf = false;
validation.required_spec_claims.clear();
decode::<Claims>(token, &DecodingKey::from_secret(b""), &validation)
.ok()
.map(|token_data| token_data.claims.user_id)
}
}
// Usage example
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_generation_and_verification() {
let config = JwtConfig::default();
let jwt_service = JwtService::new(config);
let claims = Claims::new(
123,
"testuser".to_string(),
"[email protected]".to_string(),
vec!["user".to_string()],
vec!["read".to_string(), "write".to_string()],
&jwt_service.config,
);
// Generate token
let token = jwt_service.generate_access_token(&claims).unwrap();
assert!(!token.is_empty());
// Verify token
let verified_claims = jwt_service.verify_token(&token).unwrap();
assert_eq!(verified_claims.user_id, 123);
assert_eq!(verified_claims.username, "testuser");
assert_eq!(verified_claims.email, "[email protected]");
assert_eq!(verified_claims.roles, vec!["user"]);
}
}
Axum Web Framework Integration
// src/auth.rs - Axum authentication middleware
use axum::{
async_trait,
extract::{FromRequestParts, TypedHeader},
headers::{authorization::Bearer, Authorization},
http::{request::Parts, StatusCode},
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use crate::{Claims, JwtService};
// Authentication error types
#[derive(Debug)]
pub enum AuthError {
MissingToken,
InvalidToken,
ExpiredToken,
InsufficientPermissions,
}
impl IntoResponse for AuthError {
fn into_response(self) -> Response {
let (status, message) = match self {
AuthError::MissingToken => (StatusCode::UNAUTHORIZED, "Missing authorization token"),
AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "Invalid token"),
AuthError::ExpiredToken => (StatusCode::UNAUTHORIZED, "Token expired"),
AuthError::InsufficientPermissions => (StatusCode::FORBIDDEN, "Insufficient permissions"),
};
let body = Json(json!({
"error": message,
"status": status.as_u16()
}));
(status, body).into_response()
}
}
// Extractor representing authenticated user information
pub struct AuthenticatedUser(pub Claims);
#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = AuthError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
// Extract token from Authorization header
let TypedHeader(Authorization(bearer)) = parts
.extract::<TypedHeader<Authorization<Bearer>>>()
.await
.map_err(|_| AuthError::MissingToken)?;
// Verify token using JwtService
// In real implementation, retrieve JwtService from State
let jwt_service = JwtService::new(Default::default()); // Simplified
let claims = jwt_service
.verify_token(bearer.token())
.map_err(|_| AuthError::InvalidToken)?;
Ok(AuthenticatedUser(claims))
}
}
// Role-based authentication decorator-like implementation
pub struct RequireRole(pub String);
#[async_trait]
impl<S> FromRequestParts<S> for RequireRole
where
S: Send + Sync,
{
type Rejection = AuthError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let AuthenticatedUser(claims) = AuthenticatedUser::from_request_parts(parts, state).await?;
// Role check (make configurable in real implementation)
let required_role = "admin"; // This example requires admin role
if !claims.roles.contains(&required_role.to_string()) {
return Err(AuthError::InsufficientPermissions);
}
Ok(RequireRole(required_role.to_string()))
}
}
RESTful API Implementation
// src/handlers.rs - API handler implementation
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::{AuthenticatedUser, RequireRole, JwtService, Claims};
// Application state
#[derive(Clone)]
pub struct AppState {
pub jwt_service: Arc<JwtService>,
}
// Login request
#[derive(Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
// Login response
#[derive(Serialize)]
pub struct LoginResponse {
pub access_token: String,
pub refresh_token: String,
pub token_type: String,
pub expires_in: i64,
}
// User information
#[derive(Serialize)]
pub struct UserInfo {
pub user_id: u64,
pub username: String,
pub email: String,
pub roles: Vec<String>,
pub permissions: Vec<String>,
}
// Login handler
pub async fn login_handler(
State(state): State<AppState>,
Json(login_req): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, StatusCode> {
// In real implementation, authenticate user with database
if login_req.username != "demo" || login_req.password != "password" {
return Err(StatusCode::UNAUTHORIZED);
}
// Create claims
let claims = Claims::new(
1,
login_req.username,
"[email protected]".to_string(),
vec!["user".to_string(), "admin".to_string()],
vec!["read".to_string(), "write".to_string(), "delete".to_string()],
&JwtConfig::default(),
);
// Generate tokens
let access_token = state.jwt_service
.generate_access_token(&claims)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let refresh_token = state.jwt_service
.generate_refresh_token(claims.user_id)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let response = LoginResponse {
access_token,
refresh_token,
token_type: "Bearer".to_string(),
expires_in: 24 * 3600, // 24 hours
};
Ok(Json(response))
}
// Profile handler (authentication required)
pub async fn profile_handler(
auth_user: AuthenticatedUser,
) -> Json<UserInfo> {
let claims = auth_user.0;
let user_info = UserInfo {
user_id: claims.user_id,
username: claims.username,
email: claims.email,
roles: claims.roles,
permissions: claims.permissions,
};
Json(user_info)
}
// Admin-only handler (admin role required)
pub async fn admin_only_handler(
_: RequireRole, // Requires admin role
auth_user: AuthenticatedUser,
) -> Json<serde_json::Value> {
Json(json!({
"message": "Admin area accessed successfully",
"user": auth_user.0.username,
"timestamp": chrono::Utc::now().to_rfc3339()
}))
}
// Protected resource handler
pub async fn protected_resource_handler(
Path(resource_id): Path<u64>,
auth_user: AuthenticatedUser,
) -> Result<Json<serde_json::Value>, StatusCode> {
// Check resource access permissions
if !auth_user.0.permissions.contains(&"read".to_string()) {
return Err(StatusCode::FORBIDDEN);
}
Ok(Json(json!({
"resource_id": resource_id,
"data": "This is protected resource data",
"accessed_by": auth_user.0.username,
"timestamp": chrono::Utc::now().to_rfc3339()
})))
}
// Router configuration
pub fn create_router() -> Router {
let jwt_service = Arc::new(JwtService::new(JwtConfig::default()));
let app_state = AppState { jwt_service };
Router::new()
.route("/login", post(login_handler))
.route("/profile", get(profile_handler))
.route("/admin", get(admin_only_handler))
.route("/resources/:id", get(protected_resource_handler))
.with_state(app_state)
}
RSA Public Key Cryptography Implementation
// src/rsa_jwt.rs - RSA key pair usage example
use jsonwebtoken::{
encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey,
};
use rsa::{RsaPrivateKey, RsaPublicKey, pkcs8::{EncodePrivateKey, EncodePublicKey}};
use rsa::rand_core::OsRng;
pub struct RsaJwtService {
private_key: RsaPrivateKey,
public_key: RsaPublicKey,
encoding_key: EncodingKey,
decoding_key: DecodingKey,
}
impl RsaJwtService {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
// Generate RSA key pair (2048 bits)
let mut rng = OsRng;
let private_key = RsaPrivateKey::new(&mut rng, 2048)?;
let public_key = RsaPublicKey::from(&private_key);
// PKCS#8 PEM encoding
let private_pem = private_key.to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)?;
let public_pem = public_key.to_public_key_pem(rsa::pkcs8::LineEnding::LF)?;
// Create JWT keys
let encoding_key = EncodingKey::from_rsa_pem(private_pem.as_bytes())?;
let decoding_key = DecodingKey::from_rsa_pem(public_pem.as_bytes())?;
Ok(Self {
private_key,
public_key,
encoding_key,
decoding_key,
})
}
pub fn generate_token(&self, claims: &Claims) -> JwtResult<String> {
let header = Header::new(Algorithm::RS256);
encode(&header, claims, &self.encoding_key)
}
pub fn verify_token(&self, token: &str) -> JwtResult<Claims> {
let validation = Validation::new(Algorithm::RS256);
let token_data = decode::<Claims>(token, &self.decoding_key, &validation)?;
Ok(token_data.claims)
}
// Output public key in JWKS format (OAuth2 compatible)
pub fn get_public_key_jwks(&self) -> serde_json::Value {
// In real implementation, convert RSA public key to JWKS format
json!({
"keys": [{
"kty": "RSA",
"use": "sig",
"kid": "rsa-key-1",
"alg": "RS256",
// "n": base64url(public_key.n),
// "e": base64url(public_key.e),
}]
})
}
}
Testing and Performance Measurement
// src/tests.rs - Comprehensive test suite
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn test_jwt_lifecycle() {
let jwt_service = JwtService::new(JwtConfig::default());
let claims = Claims::new(
42,
"testuser".to_string(),
"[email protected]".to_string(),
vec!["user".to_string()],
vec!["read".to_string()],
&jwt_service.config,
);
// Token generation
let token = jwt_service.generate_access_token(&claims).unwrap();
assert!(!token.is_empty());
// Token verification
let verified_claims = jwt_service.verify_token(&token).unwrap();
assert_eq!(verified_claims.user_id, 42);
assert_eq!(verified_claims.username, "testuser");
}
#[test]
fn test_expired_token() {
let mut config = JwtConfig::default();
config.expiration_hours = -1; // Past time
let jwt_service = JwtService::new(config);
let claims = Claims::new(
1,
"user".to_string(),
"[email protected]".to_string(),
vec![],
vec![],
&jwt_service.config,
);
let token = jwt_service.generate_access_token(&claims).unwrap();
// Expired token verification should fail
assert!(jwt_service.verify_token(&token).is_err());
}
#[test]
fn test_invalid_signature() {
let jwt_service1 = JwtService::new(JwtConfig {
secret_key: "secret1".to_string(),
..Default::default()
});
let jwt_service2 = JwtService::new(JwtConfig {
secret_key: "secret2".to_string(),
..Default::default()
});
let claims = Claims::new(
1,
"user".to_string(),
"[email protected]".to_string(),
vec![],
vec![],
&jwt_service1.config,
);
let token = jwt_service1.generate_access_token(&claims).unwrap();
// Verification with different secret key should fail
assert!(jwt_service2.verify_token(&token).is_err());
}
#[tokio::test]
async fn benchmark_token_operations() {
let jwt_service = JwtService::new(JwtConfig::default());
let claims = Claims::new(
1,
"benchmark_user".to_string(),
"[email protected]".to_string(),
vec!["user".to_string()],
vec!["read".to_string()],
&jwt_service.config,
);
// Token generation benchmark
let start = Instant::now();
let mut tokens = Vec::new();
for _ in 0..10_000 {
let token = jwt_service.generate_access_token(&claims).unwrap();
tokens.push(token);
}
let generation_time = start.elapsed();
println!("10,000 token generation: {:?}", generation_time);
// Token verification benchmark
let start = Instant::now();
for token in &tokens {
jwt_service.verify_token(token).unwrap();
}
let verification_time = start.elapsed();
println!("10,000 token verification: {:?}", verification_time);
// Memory usage check
let token_size = tokens[0].len();
println!("Average token size: {} bytes", token_size);
}
// Concurrent processing test
#[tokio::test]
async fn test_concurrent_operations() {
use tokio::task;
let jwt_service = Arc::new(JwtService::new(JwtConfig::default()));
let mut handles = vec![];
for i in 0..100 {
let service = jwt_service.clone();
let handle = task::spawn(async move {
let claims = Claims::new(
i,
format!("user{}", i),
format!("user{}@example.com", i),
vec!["user".to_string()],
vec!["read".to_string()],
&JwtConfig::default(),
);
let token = service.generate_access_token(&claims).unwrap();
let verified = service.verify_token(&token).unwrap();
assert_eq!(verified.user_id, i);
});
handles.push(handle);
}
// Wait for all tasks to complete
for handle in handles {
handle.await.unwrap();
}
}
}
This code example demonstrates a comprehensive implementation of the Rust jsonwebtoken library, including the latest features and best practices as of 2025. It provides guidance for building high-performance and secure authentication systems leveraging Rust's characteristics.