Expand description
§axum-gate
Flexible, type-safe authentication and authorization for axum using JWTs and optional OAuth2. Supports cookie and bearer authentication, plus an OAuth2 Authorization Code + PKCE login flow that mints a first-party JWT cookie for browser sessions. Designed for single nodes and distributed systems with multiple storage backends.
§Key Features
- Cookie and bearer JWT authentication - Choose HTTP-only cookies or Authorization: Bearer
- OAuth2 login flow builder - Authorization Code + PKCE; mints first-party JWT cookies
- Role-based access control - Hierarchical roles with supervisor inheritance
- Group-based access control - Organize users by teams, departments, or projects
- Permission system - Fine-grained permissions with deterministic hashing
- Multiple storage backends - In-memory, SurrealDB, SeaORM support
- Distributed system ready - Zero-synchronization permission system
- Pre-built handlers - Login/logout endpoints with timing attack protection
- Optional anonymous context - Install
Option<Account>andOption<RegisteredClaims> - Static token mode - Simple shared-secret bearer auth for internal services
- Audit and metrics (feature-gated) - Structured audit logs and Prometheus metrics
§Re-exports
This crate re-exports selected external crates (e.g., jsonwebtoken, cookie, uuid, axum_extra, and, behind a feature flag, prometheus) because types from these crates are part of this crate’s public API. Keeping these re-exports is intentional so users can import the exposed types from a single namespace.
§Prelude
A convenience prelude is available via axum_gate::prelude::* that re-exports the most commonly used types.
§Feature Flags
storage-surrealdb— SurrealDB repositories (see BUSL-1.1 license note)storage-seaorm— SeaORM repositoriesaudit-logging— emit structured audit eventsprometheus— export metrics for audit logging (impliesaudit-logging)insecure-fast-hash— faster Argon2 preset for development only (opt-in for release, not recommended)aws_lc_rs: Uses AWS Libcrypto for JWT cryptographic operations
For common integration issues and debugging tips, see the Troubleshooting guide.
§Quick Start
use axum::{routing::get, Router};
use axum_gate::prelude::*;
use axum_gate::repositories::memory::{MemoryAccountRepository, MemorySecretRepository};
use std::sync::Arc;
#[tokio::main]
async fn main() {
// Set up storage (dev-friendly in-memory backends)
let account_repo = Arc::new(MemoryAccountRepository::<Role, Group>::default());
let secret_repo = Arc::new(MemorySecretRepository::new_with_argon2_hasher().unwrap());
// Create a JWT codec. Use a persistent key in production (e.g., env/secret manager).
let secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "dev-secret-key".to_string());
let options = JsonWebTokenOptions {
enc_key: jsonwebtoken::EncodingKey::from_secret(secret.as_bytes()),
dec_key: jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()),
header: None,
validation: None,
};
let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::new_with_options(options));
// Protect routes with role-based access (cookie auth)
let app = Router::<()>::new()
.route("/admin", get(admin_handler))
.layer(
Gate::cookie::<_, Role, Group>("my-app", jwt)
.with_policy(AccessPolicy::require_role(Role::Admin))
.configure_cookie_template(|tpl| tpl.name("auth-token"))
.unwrap(),
);
}
async fn admin_handler() -> &'static str { "Admin access granted!" }§Access Control
§Role-Based Access
use axum_gate::prelude::{Role, Group, AccessPolicy};
// Single role requirement
let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin);
// Multiple role options
let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin)
.or_require_role(Role::Moderator);
// Hierarchical access (role + all supervisor roles)
let policy = AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::User);§Group-Based Access
use axum_gate::prelude::{Role, Group, AccessPolicy};
let policy = AccessPolicy::<Role, Group>::require_group(Group::new("engineering"))
.or_require_group(Group::new("management"));§Permission-Based Access
use axum_gate::prelude::{Role, Group, AccessPolicy, PermissionId};
// Validate permissions at compile-time (checks for hash collisions)
axum_gate::validate_permissions!["read:api", "write:api", "admin:system"];
// Use in access policies
let policy = AccessPolicy::<Role, Group>::require_permission(PermissionId::from("read:api"));§Convenient Login Check
use axum_gate::prelude::*;
use std::sync::Arc;
// Allow any authenticated user (all roles: User, Reporter, Moderator, Admin)
let gate = Gate::cookie::<_, Role, Group>("my-app", jwt_codec)
.require_login() // Convenience method for any logged-in user
.configure_cookie_template(|tpl| tpl.name("auth-token"))
.unwrap();§Authentication Modes
§Cookie (Optional User Context)
For routes that should never be blocked but may use authenticated context when present:
use axum::{routing::get, Router, extract::Extension};
use axum_gate::prelude::*;
use axum_gate::codecs::jwt::RegisteredClaims;
use std::sync::Arc;
async fn homepage(
Extension(user_opt): Extension<Option<Account<Role, Group>>>,
Extension(claims_opt): Extension<Option<RegisteredClaims>>,
) -> String {
if let (Some(user), Some(claims)) = (user_opt, claims_opt) {
format!("Welcome back {} (token expires at {})", user.user_id, claims.expiration_time)
} else {
"Welcome anonymous visitor".into()
}
}
let app = Router::<()>::new()
.route("/", get(homepage))
.layer(
Gate::cookie::<_, Role, Group>("my-app", jwt)
.allow_anonymous_with_optional_user()
);§Bearer (Strict, Optional, Static Token)
Strict bearer (JWT) example:
let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
let app = Router::<()>::new()
.route("/admin", get(handler))
.layer(
Gate::bearer("my-app", Arc::clone(&jwt))
.with_policy(AccessPolicy::<Role, Group>::require_role(Role::Admin))
);Optional mode (never blocks; installs Option<Account> and Option<RegisteredClaims>):
let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
let gate = Gate::bearer::<_, Role, Group>("my-app", jwt).allow_anonymous_with_optional_user();Static token mode (shared secret; useful for internal services):
let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
let app = axum::Router::<()>::new()
.route("/internal", axum::routing::get(handler))
.layer(Gate::bearer::<_, Role, Group>("my-app", jwt).with_static_token("very-secret-token"));§OAuth2 (Authorization Code + PKCE → first-party JWT)
Minimal setup for mounting “/auth/login” and “/auth/callback”:
use axum::{Router, routing::get};
use axum_gate::prelude::*;
use std::sync::Arc;
// Provide a JWT codec to mint the session cookie after successful callback.
let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
let oauth_routes = Gate::oauth2_with_jwt("my-app", jwt, 3600)
.auth_url("https://provider.example.com/oauth2/authorize")
.token_url("https://provider.example.com/oauth2/token")
.client_id("CLIENT_ID")
.client_secret("CLIENT_SECRET")
.redirect_url("https://your.app/auth/callback")
.add_scope("openid")
.add_scope("email")
// Map provider token response to your Account<Role, Group>:
.with_account_mapper(|_token_resp| {
Box::pin(async {
// fetch userinfo as needed, then construct Account<Role, Group>
Ok(Account::<Role, Group>::new("user@example.com", &[], &[]))
})
})
.routes("/auth")
.expect("valid oauth2 config");
let app = Router::<()>::new().nest("/auth", oauth_routes);§Account Management
use axum_gate::accounts::AccountInsertService;
use axum_gate::permissions::Permissions;
use axum_gate::prelude::{Role, Group, Account};
use axum_gate::repositories::memory::{MemoryAccountRepository, MemorySecretRepository};
use std::sync::Arc;
// Create account with roles, groups, and permissions
let account = AccountInsertService::insert("user@example.com", "password")
.with_roles(vec![Role::User])
.with_groups(vec![Group::new("staff")])
.with_permissions(Permissions::from_iter(["read:profile"]))
.into_repositories(account_repo, secret_repo)
.await;§Storage Backends
§In-Memory (Development)
use axum_gate::repositories::memory::{MemoryAccountRepository, MemorySecretRepository};
use axum_gate::prelude::{Role, Group};
use std::sync::Arc;
let account_repo = Arc::new(MemoryAccountRepository::<Role, Group>::default());
let secret_repo = Arc::new(MemorySecretRepository::new_with_argon2_hasher().unwrap());§SurrealDB (Feature: storage-surrealdb)
use axum_gate::repositories::surrealdb::{DatabaseScope, SurrealDbRepository};
use std::sync::Arc;
let repo = Arc::new(SurrealDbRepository::new(db, scope));§SeaORM (Feature: storage-seaorm)
use axum_gate::repositories::sea_orm::{SeaOrmRepository, models};
use std::sync::Arc;
let repo = Arc::new(SeaOrmRepository::new(&db));§Authentication Handlers
Pre-built route_handlers::login and route_handlers::logout handlers integrate with
your storage backends and JWT configuration. See examples in the repository for complete
implementation patterns with dependency injection and routing setup.
Note: The login handler is for username/password flows and is not used with
OAuth2Gate (which mounts its own /login and /callback routes). For sign-out,
the same logout handler remains applicable—as long as its CookieTemplate matches
the auth cookie name/template used by your OAuth2-issued first‑party JWT.
§User Data in Handlers
use axum::extract::Extension;
use axum_gate::codecs::jwt::RegisteredClaims;
use axum_gate::prelude::{Account, Role, Group};
async fn profile_handler(
Extension(user): Extension<Account<Role, Group>>,
Extension(claims): Extension<RegisteredClaims>,
) -> String {
format!(
"Hello {}, roles: {:?}, issued at: {}, expires: {}",
user.user_id, user.roles, claims.issued_at_time, claims.expiration_time
)
}§Security Features
§Cookie Security
- Secure defaults:
CookieTemplate::recommendedprovides secure defaults - HTTPS enforcement:
secure(true)cookies in production - XSS protection:
http_only(true)prevents script access - CSRF mitigation:
SameSite::Strictfor sensitive operations
§JWT Security
- Persistent keys: Use stable signing keys in production (see
JsonWebTokendocs) - Proper expiration: Set reasonable JWT expiration times
- Key rotation: Manual key replacement only; rotation invalidates existing tokens
§Timing Attack Protection
Built-in protection against timing attacks:
- Constant-time credential verification using the
subtlecrate - Always performs password verification, even for non-existent users
- Unified error responses prevent user enumeration
- Applied to all storage backends
§Audit and Metrics (feature-gated)
- Enable
audit-loggingto emit structured audit events for authentication flows - Enable
prometheus(impliesaudit-logging) to export metrics; in bearer mode you can also callwith_prometheus_metrics()orwith_prometheus_registry(..)on the gate builder - Never log sensitive values (secrets, tokens, cookies); only high-level event metadata
§Permission System
- Compile-time validation: Use
validate_permissions!macro for collision detection - Runtime validation:
permissions::PermissionCollisionCheckerfor dynamic permissions - Deterministic hashing: No coordination needed between distributed nodes
- Efficient storage: Bitmap-based permission storage with fast lookups
Note for client and WASM usage: If you’re building client-side or WebAssembly (wasm) applications and only need the crate’s data models (types) without server-only dependencies, you can depend on this crate with default features disabled. For example:
axum-gate = { version = “1”, default-features = false }
This allows using the models and core types in constrained runtimes (like wasm) while avoiding optional server features that require a full server environment.
Re-exports§
pub use axum_extra;pub use jsonwebtoken;pub use prometheus;pub use uuid;
Modules§
- accounts
- Account management and user data structures.
- audit
- Audit logging utilities for sensitive operations.
- authn
- Authentication services and workflows.
- authz
- Authorization system for role-based, group-based, and permission-based access control.
- codecs
- JWT and token encoding/decoding infrastructure.
- comma_
separated_ value - Comma-separated value conversion trait for SeaORM storage.
- cookie_
template - Secure cookie template builder for authentication cookies.
- credentials
- User credential types and verification abstractions.
- errors
- Unified, category-based error types exposed by this crate.
- gate
- Gate implementation for protecting axum routes with JWT authentication.
- groups
- Group-based access control for organizing users into logical collections.
- hashing
- Password hashing and verification services.
- permissions
- Zero-synchronization permission system using deterministic hashing.
- prelude
- Common types and functions for quick imports.
- repositories
- Repository implementations for account and secret storage.
- roles
- Pre-defined hierarchical role system for access control.
- route_
handlers - Pre-built route handlers for authentication workflows.
- secrets
- Secrets hashing and verification models.
- verification_
result - Verification value objects for domain operations.
Macros§
- validate_
permissions - Macro for test-time permission validation.