Multi-User Authentication¶
oAI-Web supports multiple users with role-based access control, TOTP MFA, and API key authentication.
Roles¶
| Role | Capabilities |
|---|---|
admin |
Full access: all settings, all audit logs, user management, credentials, global whitelists, bash tool |
user |
Own settings, own audit entries, personal API keys, browser trusted domains, no admin panels |
Session cookies¶
Sessions are HMAC-signed cookies:
Payload:
Signing secret: Auto-generated at startup, stored in credentials as system:session_secret. Rotating this secret invalidates all existing sessions.
Lifetime: 30 days from issue. No refresh mechanism — users must re-login after expiry.
Why HMAC cookies instead of JWTs?¶
Same security properties as JWT (signed, not encrypted — so user can decode payload but not forge it). Simpler implementation with fewer moving parts. No library dependency for the core verification.
Password hashing¶
Argon2id via argon2-cffi. Industry recommendation for modern password hashing (preferred over bcrypt by OWASP since 2022). Default parameters from the library are used.
MFA (TOTP)¶
Multi-factor authentication uses TOTP (RFC 6238), compatible with any authenticator app (Google Authenticator, Authy, 1Password, etc.).
Setup flow¶
- Admin calls
POST /api/admin/users/{id}/mfa-setup - Server generates a secret, returns a QR code (PNG) and provisioning URI
- User scans the QR code in their authenticator app
- On next login, user is prompted to enter the 6-digit TOTP code
- If correct (
valid_window=1= ±30 seconds tolerance), session is created
Challenge flow¶
- Username + password validated
- MFA challenge token created in
mfa_challengestable (5-minute expiry) - User redirected to
/mfapage with the challenge token - User enters TOTP code
- If valid: session cookie set, user redirected to original destination
Disabling MFA¶
Admin calls POST /api/admin/users/{id}/mfa-disable. The totp_secret is cleared. Normal password-only login resumes.
API key authentication¶
API keys are per-user tokens that can be set in Settings → Profile.
or
API key auth resolves to SYNTHETIC_API_ADMIN — a synthetic admin user with no DB entry. This means API key access always has admin privileges.
The key is stored (hashed) in user_settings["api_key"]. The plaintext is only shown once at generation time.
Auth middleware¶
_AuthMiddleware in main.py runs on every request:
- Check for API key in headers → synthetic admin user
- Decode and verify session cookie
- Check if user is active (
is_active=True) - If MFA challenge in progress: allow
/mfaand/api/auth/mfathrough - Store the resolved
CurrentUserin thecurrent_userContextVar
Public routes that bypass auth:
- /login, /setup, /health
- /webhook/* (protected by their own token mechanism)
- Static assets (/static/*)
_require_auth() vs _require_admin()¶
Route handlers call these helpers directly (dependency injection alternative):
# Allow any authenticated user
user = await _require_auth(request)
# Admin only
user = await _require_admin(request)
Both return the CurrentUser object or raise HTTPException(401/403).
First-run setup¶
If no users exist in the database, all traffic is redirected to /setup. This page creates the first admin account. After the admin is created, /setup is no longer accessible.
User folders¶
Each user optionally gets a private filesystem folder:
- Location: {system:users_base_folder}/{username}/
- Created automatically on first access
- Non-admin users get BoundFilesystemTool scoped to this folder (cannot escape it)
- Admin users get the global FilesystemTool (whitelist-based)
Set system:users_base_folder in Settings → Credentials to enable this feature.
Login rate limiting¶
server/login_limiter.py implements IP-based rate limiting for the /login endpoint:
- 5 failed attempts per 15 minutes per IP
- Lockout sends HTTP 429 with Retry-After header
- In-memory (resets on server restart)