An authentication and authorization framework for Rust.
Philosophy: Zero-cost abstractions, trait-based, async-native. Every feature is a plugin, nothing is hardcoded.
┌───────────┐ ┌─────────────┐
│ authx-cli │ │ dashboard │
└─────┬─────┘ └──────┬──────┘
│ │
┌───────────────▼───────────────▼──────────────┐
│ authx-axum (HTTP layer) │
│ SessionLayer RateLimitLayer CSRF Routes │
└──────────────────────┬───────────────────────┘
│
┌─────────────────────────────▼─────────────────────────────┐
│ authx-plugins (features) │
│ │
│ EmailPassword TOTP MagicLink PasswordReset │
│ OAuth OIDC Provider/Federation WebAuthn │
│ ApiKey EmailOTP Admin Organization │
└─────────────────────────────┬─────────────────────────────┘
│
┌─────────────────────────────▼─────────────────────────────┐
│ authx-core (zero-dep engine) │
│ │
│ Crypto (Argon2id · AES-256-GCM) JWT / EdDSA signing │
│ RBAC + ABAC policy engine EventBus │
│ Brute-force lockout Key rotation │
└─────────────────────────────┬─────────────────────────────┘
│
┌─────────────────────────────▼─────────────────────────────┐
│ authx-storage (repository ports) │
│ │
│ MemoryStore (dev/test) PostgresStore (sqlx) │
│ AuditLogger RedisTokenStore │
│ Bring your own adapter │
└───────────────────────────────────────────────────────────┘
| Constraint | Detail |
|---|---|
| Framework-agnostic core | authx-core has zero axum/actix imports |
| Storage-agnostic | Pluggable Repository traits; bring your own adapter |
| Password hashing | Argon2id only (65536 mem / 3 iter / 4 parallelism) |
| Session tokens | SHA-256 hashed before storage, raw token sent to client once |
| JWT signing | Ed25519 / EdDSA via jsonwebtoken |
| CSRF | Origin/Referer trusted-origin check for mutating requests |
crates/
authx-core/ # Models, crypto, events, RBAC/ABAC policy, identity
authx-storage/ # Repository traits + MemoryStore + PostgresStore
authx-plugins/ # Plugin trait + all auth plugins
authx-axum/ # Tower middleware, route handlers, cookies, CSRF, rate limiting
authx-cli/ # CLI binary (serve, migrate, user, key, oidc)
authx-dashboard/ # Admin dashboard (HTMX)
examples/
fullstack-app/ # End-to-end: React + Axum + PostgreSQL + SDK + TOTP MFA
axum-app/ # Full working Axum integration demo
actix-app/ # Direct actix-web integration demo
react-sdk-app/ # React consumer app for the TypeScript SDK packages
vue-sdk-app/ # Vue consumer app for the TypeScript SDK packages
packages/
authx-sdk-ts/ # Low-level TypeScript SDK: OIDC, JWKS, PKCE, device, session helpers
authx-sdk-web/ # Browser token storage, authenticated fetch, and refresh orchestration
authx-sdk-react/ # React provider/hooks for authx token clients
authx-sdk-vue/ # Vue plugin/composable for authx token clients
# Cargo.toml
[dependencies]
authx-core = "0.1"
authx-storage = "0.1"
authx-plugins = "0.1"
authx-axum = "0.1"use authx_storage::memory::MemoryStore;
use authx_plugins::EmailPasswordService;
use authx_axum::{AuthxState, SessionLayer, RequireAuth};
use authx_core::events::EventBus;
use axum::{Router, routing::get};
#[tokio::main]
async fn main() {
let store = MemoryStore::new();
let events = EventBus::new();
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET required");
let state = AuthxState::new(
store.clone(),
events.clone(),
jwt_secret,
3600, // session TTL seconds
);
let app = Router::new()
.route("/me", get(me_handler).layer(RequireAuth::new()))
.nest("/auth", authx_axum::handlers::auth_router(state.clone()))
.layer(SessionLayer::new(store, jwt_secret));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}See examples/axum-app/src/main.rs for a complete example.
use authx_plugins::EmailPasswordService;
use authx_core::brute_force::LockoutConfig;
// Basic setup
let svc = EmailPasswordService::new(store.clone(), events.clone(), 3600);
// With brute-force lockout (5 failures → 15-min lockout)
let svc = EmailPasswordService::new(store.clone(), events.clone(), 3600)
.with_lockout(LockoutConfig { max_failures: 5, window_secs: 900 });
let resp = svc.sign_up("user@example.com", "securepassword").await?;
let resp = svc.sign_in("user@example.com", "securepassword", "127.0.0.1").await?;
// resp.token — JWT, send to client
// resp.session — full Session modeluse authx_plugins::TotpService;
let svc = TotpService::new(store.clone(), "MyApp");
// Enrollment
let setup = svc.begin_setup(user_id).await?;
// setup.otpauth_uri → generate QR code, show to user
// setup.backup_codes → show once, store securely
// Confirm user can produce a valid code, then persist
svc.confirm_setup(user_id, &setup, "123456").await?;
// Verify on sign-in
svc.verify(TotpVerifyRequest { user_id, code: "123456".into() }).await?;use authx_plugins::MagicLinkService;
let svc = MagicLinkService::new(store.clone(), events.clone(), 3600);
// Issue token (send in email yourself — authx does not send email)
let token = svc.request_link("user@example.com").await?; // None if unknown
// User clicks link → verify and create session
let resp = svc.verify(&token.unwrap(), "client-ip").await?;
// resp.token → JWT session tokenuse authx_plugins::PasswordResetService;
let svc = PasswordResetService::new(store.clone(), events.clone());
// 30-minute token (send in email yourself)
let token = svc.request_reset("user@example.com").await?; // None if unknown
// User submits new password
svc.reset_password(PasswordResetRequest {
token: token.unwrap(),
new_password: "newSecurePassword123".into(),
}).await?;use authx_plugins::AdminService;
let svc = AdminService::new(store.clone(), events.clone(), 3600);
svc.ban_user(admin_id, user_id, "violated ToS").await?;
svc.unban_user(admin_id, user_id).await?;
// Impersonate — creates a tagged session for support/debugging
let (session, token) = svc.impersonate(admin_id, target_id, "admin-ip").await?;use authx_core::policy::{AuthzEngine, AuthzRequest};
use authx_core::policy::builtin::{
OrgBoundaryPolicy, RequireEmailVerifiedPolicy,
IpAllowListPolicy, TimeWindowPolicy,
};
let mut engine = AuthzEngine::new();
engine.add_policy(OrgBoundaryPolicy);
engine.add_policy(RequireEmailVerifiedPolicy::for_prefix("admin."));
engine.add_policy(IpAllowListPolicy::new(["10.0.0.0/8"]));
// engine.add_policy(TimeWindowPolicy::weekdays(9, 18)); // 09–18 UTC, Mon–Fri
// Enforce — returns Err(AuthError::Forbidden) on denial
engine.enforce("admin.delete_user", &identity, Some("org:acme-uuid:reports")).await?;use authx_core::KeyRotationStore;
let store = KeyRotationStore::new(3); // keep at most 3 key versions
store.add_key("v1", PRIV_PEM_V1, PUB_PEM_V1)?;
// Later — rotate without dropping existing token validity
store.rotate("v2", PRIV_PEM_V2, PUB_PEM_V2)?;
let token = store.sign(user_id, 3600, serde_json::Value::Null)?;
let claims = store.verify(&token)?; // tries v2 first, falls back to v1use authx_axum::rate_limit::RateLimitLayer;
use std::time::Duration;
// 20 requests per minute per IP on auth routes
let rate_limit = RateLimitLayer::new(20, Duration::from_secs(60));
let auth_routes = Router::new()
.nest("/auth", auth_router)
.layer(rate_limit);use authx_storage::AuditLogger;
// Subscribes to EventBus and writes every auth event to storage asynchronously.
AuditLogger::new(store.clone(), events.clone()).run();
// Query logs
let logs = store.find_audit_logs_by_user(user_id, 50).await?;use authx_storage::memory::MemoryStore;
let store = MemoryStore::new();authx-storage = { version = "0.1", features = ["sqlx-postgres"] }use authx_storage::PostgresStore;
let store = PostgresStore::connect("postgres://user:pass@host/db").await?;
store.migrate().await?; // runs bundled migrations automatically| Concern | Default |
|---|---|
| Password hashing | Argon2id · m=65536 · t=3 · p=4 |
| JWT algorithm | EdDSA (Ed25519) |
| Session token storage | SHA-256 hex hash only — plaintext discarded immediately |
| OAuth token storage | AES-256-GCM encrypted |
| CSRF protection | Origin/Referer trusted-origin check on all mutating methods |
| Brute-force lockout | Sliding window — configurable threshold + window |
| Rate limiting | Per-IP sliding window — configurable threshold + window |
| Magic link TTL | 15 minutes, single-use |
| Password reset TTL | 30 minutes, single-use |
| Cookie flags | HttpOnly · SameSite=Lax · Secure (configurable) · Path=/ |
cargo test --workspaceContributions are welcome! Please follow these steps:
- Fork and clone the repository.
- Create a branch from
mainfor your change. - Run the test suite before submitting:
cargo test --workspace cargo clippy --workspace -- -D warnings cargo fmt --all -- --check - Open a pull request against
main. CI runs the same checks above — all must pass.
- Keep PRs focused — one feature or fix per PR.
- Add tests for new functionality. Existing plugins are good reference (
crates/authx-plugins/src/*/tests.rs). - Public API changes should update the relevant docs and README examples.
- Follow existing code style —
rustfmtdefaults, no unsafe unless strictly necessary. - Security-sensitive changes (crypto, session handling, token storage) require extra review. Please call this out in the PR description.
Open an issue on GitHub. For security vulnerabilities, please email the maintainers directly instead of filing a public issue.