Authentication & Authorization¶
The MoE system distinguishes two completely separate authentication levels:
| Level | Entry point | Users |
|---|---|---|
| Admin backend | /login |
Admins (with is_admin = 1) |
| User portal | /user/login |
End users (all roles) |
Admin Authentication¶
Local login¶
- Fill in form: username + password + CSRF token
- Password check: bcrypt comparison via passlib
is_admin = 1must be set — regular users are rejected- Session is created:
OIDC / Authentik (optional)¶
Requires the following environment variables:
| Variable | Description |
|---|---|
AUTHENTIK_URL |
Base URL of the Authentik instance |
OIDC_CLIENT_ID |
OAuth2 client ID |
OIDC_CLIENT_SECRET |
OAuth2 client secret |
Flow:
flowchart TD
A[Admin opens /login] --> B["Button: Login with Authentik (SSO)"]
B --> C["GET /auth/login → redirect to<br/>Authentik /application/o/authorize/<br/>Scopes: openid profile email groups"]
C --> D[User authenticates with Authentik]
D --> E["GET /auth/callback?code=...<br/>token exchange /application/o/token/"]
E --> F["Admin check:<br/>user must be in group moe-admins<br/>OR is_superuser=true"]
F --> G[Create session + store OIDC token]
Admin check for OIDC:
Logout¶
- Local login: delete session
- OIDC: delete session + redirect to Authentik
end-session/endpoint
User Portal Authentication¶
Login¶
Form at /user/login:
- Enter username + password
- bcrypt password comparison
is_active = 1is checked (blocked users are rejected)- Session:
Password Reset Flow¶
flowchart TD
A["/user/forgot-password<br/>Enter email address"]
A --> B["Generate token<br/>secrets.token_urlsafe(32)"]
B --> C["Store token hash in DB:<br/>password_reset_tokens<br/>(TTL 1 hour, single-use)"]
C --> D["Send email (if SMTP configured)"]
D --> E["/user/reset-password?token=...<br/>Validate token (not expired, not yet used)"]
E --> F["Enter new password (min. 8 characters)"]
F --> G[Store bcrypt hash]
G --> H[Mark token as used]
Security behavior
At /user/forgot-password, the system always shows the message "If an account exists..." — regardless of whether the email is actually registered. This prevents account enumeration.
API Key Authentication¶
API keys are used for API requests to the orchestrator. No session cookie, no login — stateless.
Supported Headers¶
Validation Flow¶
flowchart TD
REQ[Incoming request] --> P[Parse header → extract key]
P --> H["Compute SHA-256:<br/>hash = sha256(key)"]
H --> L["Valkey lookup:<br/>GET user:apikey:{hash}"]
L -->|Cache hit<br/>TTL 5 minutes| CHK
L -->|Cache miss| SQL["PostgreSQL lookup:<br/>SELECT ... FROM api_keys<br/>WHERE key_hash = ? AND is_active = 1<br/>JOIN users WHERE is_active = 1"]
SQL --> POP["Populate Valkey cache<br/>TTL 300s"]
POP --> CHK
CHK["Check user object:<br/>- is_active == 1?<br/>- Budget not exceeded?<br/>- Permissions for requested resource?"]
CHK --> EX["Execute or reject request<br/>401 / 403 / 429"]
Valkey Schema¶
user:apikey:{sha256-hash} → HASH
user_id STRING
username STRING
role STRING (user|subscriber|expert|admin)
is_active STRING (1|0)
daily_limit STRING (integer or empty = unlimited)
monthly_limit STRING
total_limit STRING
permissions STRING (JSON: {resource_type: [id, ...]})
cost_factor STRING (float)
TTL: 300 seconds
CSRF Protection¶
All forms in the Admin backend and User Portal are CSRF-protected:
- Token generated per session:
secrets.token_hex(16) - Validated server-side with
secrets.compare_digest() - Session lifetime: max 8 hours (
SESSION_MAX_AGE = 28800)
Session Configuration¶
Admin Impersonation¶
Admins can take over user sessions:
- Admin session is checked (
is_admin = 1) - User session is set for the current session:
- Redirect to
/user/dashboard - Orange impersonation banner appears
Exit:
Resets admin session, clears user impersonation flags.Security Overview¶
| Mechanism | Implementation |
|---|---|
| Password hashing | bcrypt (passlib) |
| API key storage | SHA-256 hash, never plaintext |
| CSRF protection | HMAC-based session token |
| Session TTL | 8 hours |
| Valkey cache TTL | 5 minutes (API keys) |
| OIDC group | moe-admins |
| Password reset TTL | 1 hour, single-use |
| Budget enforcement | Valkey counter + orchestrator check |