axum-gate 1.1.0

Flexible authentication and authorization for Axum with JWT cookies or bearer tokens, optional OAuth2, and role/group/permission RBAC. Suitable for single-node and distributed systems.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#![deny(missing_docs)]
#![deny(unsafe_code)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]

//! # 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>` and `Option<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](https://github.com/emirror-de/axum-gate?tab=readme-ov-file#msrv-and-license))!
//! - `storage-seaorm` — SeaORM repositories (original `sea-orm` crate; mutually exclusive with `storage-seaorm-v2`)
//! - `storage-seaorm-v2` — SeaORM v2 repositories
//! - `audit-logging` — emit structured audit events
//! - `prometheus` — export metrics for audit logging (implies `audit-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
//!
//! Note: `storage-seaorm` and `storage-seaorm-v2` are mutually exclusive. Enable only one of these features per build to avoid compilation/feature conflicts.
//!
//! For common integration issues and debugging tips, [see the Troubleshooting guide](https://github.com/emirror-de/axum-gate/blob/nightly/TROUBLESHOOTING.md).
//!
//! ## Quick Start
//!
//! ```rust
//! 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
//! ```rust
//! 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
//! ```rust
//! 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
//! ```rust
//! 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
//! ```rust
//! use axum_gate::prelude::*;
//! use std::sync::Arc;
//!
//! # let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
//! // 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:
//!
//! ```rust
//! 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 jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
//! 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:
//! ```rust
//! # use axum::{routing::get, Router};
//! # use axum_gate::prelude::*;
//! # use std::sync::Arc;
//! # async fn handler() {}
//! 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>`):
//! ```rust
//! # use axum_gate::prelude::*;
//! # use std::sync::Arc;
//! 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):
//! ```rust
//! # use axum_gate::prelude::*;
//! # use std::sync::Arc;
//! # async fn handler() {}
//! 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":
//! ```rust
//! 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
//!
//! ```rust
//! 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;
//!
//! # tokio_test::block_on(async {
//! # let account_repo = Arc::new(MemoryAccountRepository::<Role, Group>::default());
//! # let secret_repo = Arc::new(MemorySecretRepository::new_with_argon2_hasher().unwrap());
//! // 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)
//! ```rust
//! 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`)
//! ```rust
//! # #[cfg(feature="storage-surrealdb")]
//! # {
//! use axum_gate::repositories::surrealdb::{DatabaseScope, SurrealDbRepository};
//! use std::sync::Arc;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let db: surrealdb::Surreal<surrealdb::engine::any::Any> = todo!();
//! # let scope = DatabaseScope::default();
//! let repo = Arc::new(SurrealDbRepository::new(db, scope));
//! # Ok(())
//! # }
//! # }
//! ```
//!
//! ### SeaORM (Feature: `storage-seaorm`)
//! ```rust
//! # #[cfg(feature="storage-seaorm")]
//! # {
//! use axum_gate::repositories::sea_orm::{SeaOrmRepository, models};
//! use std::sync::Arc;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! # let db: sea_orm::DatabaseConnection = todo!();
//! let repo = Arc::new(SeaOrmRepository::new(&db));
//! # Ok(())
//! # }
//! # }
//! ```
//!
//! ## 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
//!
//! ```rust
//! 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::recommended`](cookie_template::CookieTemplate::recommended) provides secure defaults
//! - **HTTPS enforcement**: `secure(true)` cookies in production
//! - **XSS protection**: `http_only(true)` prevents script access
//! - **CSRF mitigation**: `SameSite::Strict` for sensitive operations
//!
//! ### JWT Security
//! - **Persistent keys**: Use stable signing keys in production (see [`JsonWebToken`](codecs::jwt::JsonWebToken) docs)
//! - **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 [`subtle`] crate
//! - 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-logging` to emit structured audit events for authentication flows
//! - Enable `prometheus` (implies `audit-logging`) to export metrics; in bearer mode you can
//!   also call `with_prometheus_metrics()` or `with_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::PermissionCollisionChecker`] for 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.

#[cfg(feature = "server")]
pub use axum_extra;
#[cfg(feature = "server")]
pub use cookie;
#[cfg(feature = "server")]
pub use jsonwebtoken;
#[cfg(all(feature = "server", feature = "prometheus"))]
pub use prometheus;
pub use uuid;
pub mod accounts;
#[cfg(feature = "server")]
pub mod audit;
#[cfg(feature = "server")]
pub mod authn;
pub mod authz;
#[cfg(feature = "server")]
pub mod codecs;
#[cfg(all(
    feature = "server",
    any(feature = "storage-seaorm", feature = "storage-seaorm-v2")
))]
pub mod comma_separated_value;
#[cfg(feature = "server")]
pub mod cookie_template;
pub mod credentials;
#[cfg(feature = "server")]
pub mod errors;
#[cfg(feature = "server")]
pub mod gate;
pub mod groups;
#[cfg(feature = "server")]
pub mod hashing;
pub mod permissions;
pub mod prelude;
#[cfg(feature = "server")]
pub mod repositories;
pub mod roles;
#[cfg(feature = "server")]
pub mod route_handlers;
#[cfg(feature = "server")]
pub mod secrets;
#[cfg(feature = "server")]
pub mod verification_result;