| Crates.io | axum-jwt-sessions |
| lib.rs | axum-jwt-sessions |
| version | 0.1.1 |
| created_at | 2025-07-11 21:55:00.750271+00 |
| updated_at | 2025-07-11 22:55:21.963718+00 |
| description | JWT authentication library for Axum with refresh token support |
| homepage | |
| repository | https://github.com/Dreaming-Codes/axum-jwt-sessions |
| max_upload_size | |
| id | 1748526 |
| size | 173,694 |
A flexible JWT authentication library for Axum with refresh token support and user-based session management.
[dependencies]
axum-jwt-sessions = "0.1.0"
# For Cloudflare Workers KV support
axum-jwt-sessions = { version = "0.1.0", features = ["cloudflare-kv"] }
use axum_jwt_sessions::prelude::*;
use time::Duration;
use uuid::Uuid;
// Define your session data
#[derive(Debug, Clone, Serialize, Deserialize)]
struct UserSession {
user_id: String,
email: String,
roles: Vec<String>,
}
// Implement SessionStorage trait for user-based session management
struct MyStorage;
impl SessionStorage for MyStorage {
// Create a new session for a user
async fn create_user_session(
&self,
user_id: String,
session_id: Uuid,
expires_at: OffsetDateTime,
) -> Result<()> {
// Store session data in user's session array
Ok(())
}
// Get all active sessions for a user
async fn get_user_sessions(&self, user_id: &str) -> Result<Vec<SessionData>> {
// Return all active sessions for this user
Ok(vec![])
}
// Revoke a specific session for a user
async fn revoke_user_session(&self, user_id: &str, session_id: &Uuid) -> Result<()> {
// Remove specific session from user's session array
Ok(())
}
// Revoke all sessions for a user
async fn revoke_all_user_sessions(&self, user_id: &str) -> Result<()> {
// Clear all sessions for this user
Ok(())
}
}
// Implement SessionDataRefresher to fetch fresh data during token refresh
impl SessionDataRefresher for MyStorage {
type SessionData = UserSession;
async fn refresh_session_data(&self, user_id: &str) -> Result<Option<Self::SessionData>> {
// Fetch fresh user data from your database using user_id
// This ensures tokens always have up-to-date information
Ok(Some(UserSession {
user_id: user_id.to_string(),
email: "user@example.com".to_string(),
roles: vec!["user".to_string()],
}))
}
}
// Configure JWT settings
let jwt_config = JwtConfig {
access_token_secret: "access-secret".to_string(),
refresh_token_secret: "refresh-secret".to_string(),
issuer: Some("my-app".to_string()),
audience: Some("my-app-users".to_string()),
access_token_duration: Duration::minutes(15),
refresh_token_duration: Duration::days(7),
refresh_token_renewal_threshold: Duration::days(1),
};
// Create auth state
let storage = Arc::new(MyStorage);
let auth_state = AuthState {
token_generator: Arc::new(TokenGenerator::new(jwt_config)),
storage: storage.clone(),
refresher: storage,
};
// Login endpoint
async fn login(State(state): State<AuthState<MyStorage, MyStorage>>) -> Result<Json<TokenResponse>> {
let user_id = "user-123".to_string();
let session_data = UserSession {
user_id: user_id.clone(),
email: "user@example.com".to_string(),
roles: vec!["user".to_string()],
};
let session_id = Uuid::new_v4();
// Generate tokens with session data embedded in access token
let tokens = state.token_generator.generate_token_pair(
session_id,
user_id.clone(),
Some(session_data),
)?;
// Create session in user-based storage
state.storage.create_user_session(
user_id,
session_id,
tokens.refresh_expires_at,
).await?;
Ok(Json(TokenResponse {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
}))
}
// Use in routes - session data comes from the JWT
async fn protected_route(session: Session<UserSession>) -> String {
format!("Hello, {}", session.data.email)
}
async fn optional_auth_route(session: OptionalSession<UserSession>) -> String {
match session.0 {
Some(s) => format!("Hello, {}", s.data.email),
None => "Hello, anonymous".to_string(),
}
}
The library now organizes sessions by user_id, allowing multiple sessions per user:
// List all sessions for a user
async fn list_user_sessions(
State(state): State<AuthState<MyStorage, MyStorage>>,
session: Session<UserSession>,
) -> Result<Json<Vec<SessionData>>> {
let sessions = state.storage.get_user_sessions(&session.data.user_id).await?;
Ok(Json(sessions))
}
// Logout from current session only
async fn logout(
State(state): State<AuthState<MyStorage, MyStorage>>,
session: Session<UserSession>,
) -> Result<&'static str> {
state.storage.revoke_user_session(
&session.data.user_id,
&session.session_id,
).await?;
Ok("Logged out successfully")
}
// Logout from all sessions (all devices)
async fn logout_all(
State(state): State<AuthState<MyStorage, MyStorage>>,
session: Session<UserSession>,
) -> Result<&'static str> {
state.storage.revoke_all_user_sessions(&session.data.user_id).await?;
Ok("Logged out from all devices")
}
The library provides flexible middleware configuration:
// Standard authentication (access token only)
.layer(middleware::from_fn_with_state(
auth_state.clone(),
auth_middleware,
))
// High-security endpoints (requires refresh token)
.layer(middleware::from_fn_with_state(
auth_state.clone(),
require_refresh_token,
))
The library includes a built-in refresh handler that fetches fresh session data when refreshing tokens:
app.route("/refresh", post(refresh_handler::<MyStorage, MyStorage>))
The refresh handler:
SessionDataRefresher trait to fetch up-to-date session dataEach session is represented by a SessionData struct:
#[derive(Debug, Clone, Serialize)]
pub struct SessionData {
pub session_id: Uuid,
pub expires_at: OffsetDateTime,
}
This library follows JWT best practices by storing session data directly in the JWT access token, while using user-based storage for refresh token management. This approach provides:
The storage layer is used for:
{
"sub": "session-uuid",
"user_id": "user-123",
"exp": 1234567890,
"iat": 1234567890,
"token_type": "access",
"session_data": {
"user_id": "user-123",
"email": "user@example.com",
"roles": ["user"]
}
}
{
"sub": "session-uuid",
"user_id": "user-123",
"exp": 1234567890,
"iat": 1234567890,
"token_type": "refresh"
}
RefreshSession extractor, both access and refresh tokens must be provided and have matching subjectsSee the examples/ directory for complete working examples:
basic_usage.rs - Simple authentication setup with user-based sessionsadvanced_usage.rs - User management with roles and multiple session handlingwith_middleware.rs - Using authentication middlewaresecure_paths.rs - Implementing high-security endpoints with refresh token requirementswith_openapi.rs - OpenAPI documentation with utoipa and Scalar UIThe library provides built-in support for Cloudflare Workers KV as a session storage backend, perfect for serverless applications.
Enable the cloudflare-kv feature:
[dependencies]
axum-jwt-sessions = { version = "0.1", features = ["cloudflare-kv"] }
worker = "0.6"
use axum_jwt_sessions::{CloudflareKvStorage, prelude::*};
use worker::kv::KvStore;
// In your Cloudflare Worker
#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
// Get your KV namespace
let kv = env.kv("SESSION_STORAGE")?;
// Create the storage instance
let storage = CloudflareKvStorage::new(kv);
// Configure and use with AuthState
let auth_state = AuthState::new(jwt_config, Arc::new(storage));
// Use with your Axum application
Ok(Response::ok("Success")?)
}
For detailed setup instructions and configuration options, see docs/cloudflare-kv.md.
The library provides built-in OpenAPI documentation support through the utoipa crate when the openapi feature is enabled.
Add the openapi feature to your dependencies:
[dependencies]
axum-jwt-sessions = { version = "0.1", features = ["openapi"] }
When the openapi feature is enabled, the following types automatically implement ToSchema:
RefreshRequest - Request body for token refreshRefreshResponse - Response from token refreshSessionData - Session informationErrorResponse - Authentication error responsesTokenType - Token type enumeration (access/refresh)use utoipa::{OpenApi, ToSchema};
use axum_jwt_sessions::openapi::JwtSecurityScheme;
use axum_jwt_sessions::typed_refresh_handler;
use utoipa_axum::{router::OpenApiRouter, routes};
// Define your session data with ToSchema
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
struct UserSession {
user_id: String,
email: String,
}
// Document your endpoints
#[utoipa::path(
post,
path = "/login",
request_body = LoginRequest,
responses(
(status = 200, description = "Success", body = LoginResponse),
(status = 401, description = "Invalid credentials"),
),
tag = "auth"
)]
async fn login(/* ... */) -> Result<Json<LoginResponse>> {
// Implementation
}
// Create the base OpenAPI documentation
#[derive(OpenApi)]
#[openapi(
modifiers(&JwtSecurityScheme),
tags(
(name = "auth", description = "Authentication endpoints")
)
)]
struct ApiDoc;
// Create a typed refresh handler (required for routes! macro)
typed_refresh_handler!(refresh, MyStorage, MyRefresher);
// Build router with automatic path collection
let (router, api) = OpenApiRouter::new()
.routes(routes!(login))
.routes(routes!(refresh))
.routes(routes!(logout))
.routes(routes!(protected))
.with_state(auth_state)
.split_for_parts();
// Merge the generated API with base documentation
let mut api_doc = ApiDoc::openapi();
api_doc.merge(api);
// Serve the OpenAPI JSON and Scalar UI
let api_doc = Arc::new(api_doc);
let app = router
.route("/api-docs/openapi.json", get({
let doc = api_doc.clone();
move || async move { Json(doc.as_ref().clone()) }
}))
.merge(Scalar::with_url("/scalar", api_doc.as_ref().clone()));
Add the necessary dependencies:
[dependencies]
utoipa-axum = "0.2"
utoipa-scalar = { version = "0.3", features = ["axum"] }
The library provides JwtSecurityScheme which automatically adds JWT Bearer authentication to your OpenAPI documentation:
#[utoipa::path(
get,
path = "/protected",
responses(
(status = 200, description = "Success", body = UserData),
(status = 401, description = "Unauthorized", body = ErrorResponse),
),
security(("bearer_auth" = [])),
tag = "auth"
)]
async fn protected(session: Session<UserData>) -> Json<UserData> {
Json(session.data)
}
The example uses utoipa-axum for automatic path collection and utoipa-scalar for beautiful, interactive API documentation:
use utoipa_axum::{router::OpenApiRouter, routes};
use utoipa_scalar::{Scalar, Servable};
use axum_jwt_sessions::typed_refresh_handler;
// Create a typed refresh handler for use with routes!
typed_refresh_handler!(refresh, MyStorage, MyRefresher);
// Build router with automatic OpenAPI integration
let (router, api) = OpenApiRouter::new()
.routes(routes!(your_endpoints))
.routes(routes!(refresh))
.with_state(auth_state)
.split_for_parts();
This provides:
typed_refresh_handler! macro/scalarFor a complete example, see examples/with_openapi.rs.
If upgrading from a session-based storage implementation, you need to:
session_id primary keys to user_id primary keysuser_idThe library maintains backward compatibility for the JWT token structure while providing the new user-based storage capabilities.