Passwordless auth for Rust apps
FutureAuth is an authentication service with a Rust SDK (futureauth crate). It provides magic link and OTP-based passwordless authentication for Rust/Axum web applications. The SDK handles all auth logic locally — users, sessions, and verification codes live in your own Postgres database. FutureAuth's server only delivers OTP codes via email (Resend) or SMS (Twilio).
Links: crates.io · GitHub SDK · Sample Project · llms.txt · OpenAPI Docs
cargo add futureauth --features axum-integration
Or in Cargo.toml:
[dependencies] futureauth = { version = "0.4", features = ["axum-integration"] }
use futureauth::{FutureAuth, FutureAuthConfig}; use sqlx::PgPool; use std::sync::Arc; let pool = PgPool::connect(&std::env::var("DATABASE_URL")?).await?; let auth = FutureAuth::new(pool.clone(), FutureAuthConfig { api_url: "https://future-auth.com".to_string(), secret_key: std::env::var("FUTUREAUTH_SECRET_KEY")?, project_name: "My App".to_string(), ..Default::default() }); // Creates user, session, verification tables (idempotent) auth.ensure_tables().await?;
IMPORTANT: Use .merge(), NOT .nest(). Routes already have /api/auth/ prefix.
.merge()
.nest()
/api/auth/
use futureauth::axum::{auth_router, AuthSession}; #[derive(Clone)] struct AppState { db: PgPool, auth: Arc<FutureAuth>, } impl AsRef<Arc<FutureAuth>> for AppState { fn as_ref(&self) -> &Arc<FutureAuth> { &self.auth } } let state = AppState { db: pool, auth: Arc::new(auth) }; let app = Router::new() .merge(futureauth::axum::auth_router(state.auth.clone())) .route("/api/me", get(me_handler)) .with_state(state); async fn me_handler(auth: AuthSession) -> Json<serde_json::Value> { Json(serde_json::json!({ "id": auth.user.id, "email": auth.user.email })) }
// Magic link (one-click email sign-in) auth.send_magic_link("user@example.com", "https://myapp.com/auth/verify").await?; // Email OTP auth.send_otp(OtpChannel::Email, "user@example.com").await?; // Phone OTP (SMS) auth.send_otp(OtpChannel::Phone, "+15551234567").await?;
// Verify OTP let (user, session) = auth.verify_otp( "user@example.com", "a1b2c3", Some("127.0.0.1"), Some("Mozilla/5.0"), ).await?; // Verify magic link let (user, session) = auth.verify_magic_link( &token, Some("127.0.0.1"), Some("Mozilla/5.0"), ).await?; // Set cookie: name = "futureauth_session", value = session.token
// Check session match auth.get_session(&token).await? { Some((user, session)) => { /* authenticated */ } None => { /* invalid/expired */ } } // Sign out auth.revoke_session(&token).await?;
/api/auth/send-magic-link
{ "email": "user@example.com", "callback_url": "https://myapp.com/auth/verify" }
/api/auth/verify-magic-link
{ "token": "..." }
/api/auth/send-otp
{ "email": "..." }
{ "phone": "+1..." }
/api/auth/verify-otp
{ "email": "...", "code": "a1b2c3" }
/api/auth/session
/api/auth/sign-out
Do NOT use better-auth or any third-party auth client. Call FutureAuth endpoints directly:
better-auth
// Send OTP await fetch("/api/auth/send-otp", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ email: "user@example.com" }), }); // Verify OTP (sets session cookie automatically) const res = await fetch("/api/auth/verify-otp", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ email: "user@example.com", code: "a1b2c3" }), }); // Check session const session = await fetch("/api/auth/session", { credentials: "include" }).then(r => r.json()); // Sign out await fetch("/api/auth/sign-out", { method: "POST", credentials: "include" });
Always include credentials: "include" on every fetch call.
credentials: "include"
FutureAuthConfig { api_url: String, // FutureAuth server URL (https://future-auth.com) secret_key: String, // Project secret key from dashboard (vx_sec_...) project_name: String, // Shown in OTP emails/SMS session_ttl: Duration, // Default: 30 days otp_ttl: Duration, // Default: 10 minutes otp_length: usize, // Default: 6 chars (alphanumeric) cookie_name: String, // Default: "futureauth_session" }
DATABASE_URL
FUTUREAUTH_SECRET_KEY
FUTUREAUTH_API_URL
"user"
session
verification
.nest("/api/auth", auth_router())
.merge(auth_router())
fetch()
/api/auth/get-session