MFA Enforcement
MFA Enforcement
Section titled “MFA Enforcement”This guide covers MFA (Multi-Factor Authentication) enforcement for the Arbitex AI Gateway platform: configuring org-level MFA policies via the admin API, understanding the three enforcement levels, protecting sensitive endpoints with MFA step-up, handling 403 + X-MFA-Required responses, and the administrator setup walkthrough.
Overview
Section titled “Overview”Arbitex supports three organization-wide MFA enforcement levels (platform-0048):
| Level | Behavior | Use Case |
|---|---|---|
off | MFA never required; users may optionally enroll | Development, internal tools |
optional | Users who have enrolled MFA must use it; un-enrolled users pass without MFA | Migration period, low-risk orgs |
required | All users must complete MFA enrollment before accessing the platform | Production, regulated industries |
Additionally, sensitive endpoints enforce MFA step-up regardless of org policy — even in optional mode, accessing these endpoints requires a fresh MFA assertion.
MFA Policy API
Section titled “MFA Policy API”Get Current MFA Policy
Section titled “Get Current MFA Policy”GET /api/admin/org/mfa-policyAuthorization: Bearer <admin-token>Response:
{ "enforcement_level": "optional", "sensitive_endpoints_require_mfa": true, "mfa_methods": ["totp", "webauthn"], "grace_period_hours": 0, "mfa_assertion_ttl_seconds": 3600, "enrollment_deadline": null, "created_at": "2026-01-15T10:00:00Z", "updated_at": "2026-03-01T09:00:00Z"}Response fields:
| Field | Type | Description |
|---|---|---|
enforcement_level | string | off | optional | required |
sensitive_endpoints_require_mfa | boolean | Always enforce MFA for sensitive endpoints |
mfa_methods | array | Enabled MFA methods: totp, webauthn, sms |
grace_period_hours | integer | Hours before required kicks in for new enrollments |
mfa_assertion_ttl_seconds | integer | How long a step-up MFA assertion is valid (default: 3600) |
enrollment_deadline | string|null | ISO-8601 date by which all users must enroll (for required) |
Update MFA Policy
Section titled “Update MFA Policy”PUT /api/admin/org/mfa-policyAuthorization: Bearer <admin-token>Content-Type: application/jsonRequest body:
{ "enforcement_level": "required", "mfa_methods": ["totp", "webauthn"], "grace_period_hours": 48, "mfa_assertion_ttl_seconds": 3600, "enrollment_deadline": "2026-04-01T00:00:00Z"}Response: 200 OK with the updated policy object.
Partial update — change only enforcement level:
curl -X PUT \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enforcement_level": "required"}' \ https://api.arbitex.example.com/api/admin/org/mfa-policyImportant: Changing from off → required directly will immediately block all non-MFA-enrolled users. Use a grace period or set enrollment_deadline to give users time to enroll.
Enforcement Levels in Detail
Section titled “Enforcement Levels in Detail”Level: off
Section titled “Level: off”- No MFA prompts at login or during the session
- Users can voluntarily enroll MFA through their profile settings
- Sensitive endpoint step-up still applies if
sensitive_endpoints_require_mfa: true - Recommended only for development environments or internal-only deployments
Level: optional
Section titled “Level: optional”- Users without MFA enrolled: pass through without MFA
- Users with MFA enrolled: must complete MFA at each login (TOTP or WebAuthn)
- Effective for organizations migrating to MFA — enrolled users are protected; others aren’t yet required to enroll
- Session tokens carry an
mfa_verified: true|falseflag - Sensitive endpoints check
mfa_verifiedregardless of enrollment status
Migration strategy:
Phase 1: enforcement_level = "optional" → encourage enrollment, monitor adoptionPhase 2: Set enrollment_deadline → notify users of upcoming requirementPhase 3: enforcement_level = "required" → block access for un-enrolled usersLevel: required
Section titled “Level: required”-
All users must have at least one MFA method enrolled before logging in
-
Login attempt without MFA enrollment returns:
HTTP/1.1 403 ForbiddenX-MFA-Required: enrollContent-Type: application/json{"error": "mfa_enrollment_required","message": "Your organization requires MFA enrollment. Please enroll a method at /settings/mfa before continuing.","enroll_url": "https://app.arbitex.example.com/settings/mfa"} -
If
grace_period_hours > 0, newly created users can access the platform for that many hours before MFA is required
Sensitive Endpoint Protection
Section titled “Sensitive Endpoint Protection”Certain endpoints always require a fresh MFA step-up assertion, regardless of enforcement_level. This is controlled by the sensitive_endpoints_require_mfa policy flag.
Sensitive Endpoint List
Section titled “Sensitive Endpoint List”| Endpoint | Reason |
|---|---|
PUT /api/admin/org/mfa-policy | MFA policy changes |
POST /api/admin/users/{id}/api-keys | Creating API keys |
DELETE /api/admin/users/{id}/api-keys/{key_id} | Revoking API keys |
PUT /api/admin/org/billing | Billing configuration changes |
POST /api/admin/gdpr/deletion-requests | GDPR deletion |
PUT /api/admin/retention/policy | Data retention changes |
POST /api/admin/outposts/{id}/rotate-cert | Certificate rotation |
DELETE /api/admin/users/{id} | User deletion |
PUT /api/admin/org/saml-providers/{id} | SAML IdP modifications |
POST /api/admin/kill-switch | Provider kill switch |
MFA Step-Up Flow
Section titled “MFA Step-Up Flow”When an unauthenticated MFA assertion is detected for a sensitive endpoint:
1. Client requests sensitive endpoint (no MFA assertion):
DELETE /api/admin/users/usr_01HXYZ HTTP/1.1Authorization: Bearer <session-token>2. Platform responds with 403 + X-MFA-Required header:
HTTP/1.1 403 ForbiddenX-MFA-Required: step_upX-MFA-Challenge-ID: mfa_chal_01HXYZContent-Type: application/json
{ "error": "mfa_required", "message": "This action requires MFA verification.", "challenge_id": "mfa_chal_01HXYZ", "expires_in": 600, "methods": ["totp", "webauthn"]}3. Client completes MFA challenge:
# TOTP completioncurl -X POST \ -H "Authorization: Bearer $SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "challenge_id": "mfa_chal_01HXYZ", "method": "totp", "code": "123456" }' \ https://api.arbitex.example.com/api/auth/mfa/verify
# Response — MFA assertion token{ "mfa_assertion_token": "mfa_ast_01HXYZ...", "expires_at": "2026-03-12T11:00:00Z", "ttl_seconds": 3600}4. Client retries sensitive endpoint with MFA assertion:
DELETE /api/admin/users/usr_01HXYZ HTTP/1.1Authorization: Bearer <session-token>X-MFA-Assertion: mfa_ast_01HXYZ...5. Platform processes request:
HTTP/1.1 204 No ContentMFA Assertion Caching
Section titled “MFA Assertion Caching”MFA assertion tokens are cached in Redis (mfa_verified:{assertion_token}) with the TTL configured by mfa_assertion_ttl_seconds. Within this window, the client can access any sensitive endpoint without re-completing MFA.
The assertion token is scope-limited — it does not grant elevated API permissions, only satisfies the MFA check. Session tokens retain their original authorization scope.
Supported MFA Methods
Section titled “Supported MFA Methods”TOTP (Time-Based One-Time Password)
Section titled “TOTP (Time-Based One-Time Password)”Compatible with any RFC 6238 authenticator app (Google Authenticator, Authy, 1Password, etc.).
Enrollment flow:
- User navigates to
/settings/mfa/enroll/totp - Platform generates a TOTP secret and QR code
- User scans QR code with authenticator app
- User submits a 6-digit code to confirm enrollment
- Platform stores the encrypted TOTP secret and marks TOTP as enrolled
Admin view of user TOTP status:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \ https://api.arbitex.example.com/api/admin/users/usr_01HXYZ/mfa-status
# Response{ "user_id": "usr_01HXYZ", "mfa_enrolled": true, "methods": [ { "type": "totp", "enrolled_at": "2026-02-15T14:30:00Z", "last_used_at": "2026-03-12T09:00:00Z" } ], "last_mfa_at": "2026-03-12T09:00:00Z"}WebAuthn (Passkeys / Hardware Keys)
Section titled “WebAuthn (Passkeys / Hardware Keys)”Supports hardware security keys (YubiKey, etc.) and platform passkeys (Touch ID, Face ID, Windows Hello).
Enrollment: Users navigate to /settings/mfa/enroll/webauthn and follow the browser’s passkey enrollment flow. The platform stores the credential public key (never the private key).
Admin notes:
- WebAuthn is phishing-resistant — always bound to the origin (
api.arbitex.example.com) - Users can register multiple WebAuthn credentials (backup key recommended)
SMS (Optional — Requires Carrier Integration)
Section titled “SMS (Optional — Requires Carrier Integration)”SMS OTP is supported but not recommended for high-security environments due to SIM-swapping risk. Enable via:
curl -X PUT \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"mfa_methods": ["totp", "webauthn", "sms"]}' \ https://api.arbitex.example.com/api/admin/org/mfa-policyRequires SMS_PROVIDER environment variable (e.g., twilio) and provider credentials.
Admin Setup Walkthrough
Section titled “Admin Setup Walkthrough”Step 1: Assess Current Enrollment Status
Section titled “Step 1: Assess Current Enrollment Status”Before enforcing MFA, check the current enrollment rate across your org:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.example.com/api/admin/users?fields=id,email,mfa_enrolled&limit=1000" \ | jq '[.users[] | select(.mfa_enrolled == false)] | length'Or use the summary endpoint:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \ https://api.arbitex.example.com/api/admin/org/mfa-summary
# Response{ "total_users": 250, "enrolled": 187, "not_enrolled": 63, "enrollment_rate": 0.748, "by_method": { "totp": 150, "webauthn": 82, "sms": 12 }}Step 2: Enable Optional MFA
Section titled “Step 2: Enable Optional MFA”Start with optional to allow enrolled users to use MFA without blocking un-enrolled users:
curl -X PUT \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "enforcement_level": "optional", "mfa_methods": ["totp", "webauthn"], "sensitive_endpoints_require_mfa": true }' \ https://api.arbitex.example.com/api/admin/org/mfa-policyStep 3: Communicate Enrollment Deadline
Section titled “Step 3: Communicate Enrollment Deadline”Use the org notification API or your own communication channel to notify users:
# Send in-app enrollment reminder to all un-enrolled userscurl -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "target": "unenrolled_users", "message_type": "mfa_enrollment_reminder", "enrollment_deadline": "2026-04-01T00:00:00Z" }' \ https://api.arbitex.example.com/api/admin/notifications/sendStep 4: Set Enrollment Deadline and Grace Period
Section titled “Step 4: Set Enrollment Deadline and Grace Period”curl -X PUT \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "enforcement_level": "optional", "enrollment_deadline": "2026-04-01T00:00:00Z", "grace_period_hours": 48 }' \ https://api.arbitex.example.com/api/admin/org/mfa-policyStep 5: Enforce Required MFA
Section titled “Step 5: Enforce Required MFA”After the enrollment deadline has passed and enrollment rate is acceptable:
curl -X PUT \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "enforcement_level": "required", "grace_period_hours": 0 }' \ https://api.arbitex.example.com/api/admin/org/mfa-policyStep 6: Handle Locked-Out Users
Section titled “Step 6: Handle Locked-Out Users”If a user loses their MFA device:
# Generate a one-time bypass code (admin use only)curl -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ https://api.arbitex.example.com/api/admin/users/usr_01HXYZ/mfa-bypass-code
# Response — single-use, expires in 1 hour{ "bypass_code": "XXXX-XXXX-XXXX-XXXX", "expires_at": "2026-03-12T12:00:00Z", "note": "Code is single-use. User must re-enroll MFA after using this bypass."}Alternatively, reset the user’s MFA enrollment:
# Reset all MFA methods for a user (requires MFA step-up on admin account)curl -X DELETE \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "X-MFA-Assertion: $MFA_ASSERTION_TOKEN" \ https://api.arbitex.example.com/api/admin/users/usr_01HXYZ/mfa-enrollment
# User will be prompted to re-enroll at next loginMonitoring MFA Events
Section titled “Monitoring MFA Events”MFA events appear in the audit log with the following action types:
| Audit Action | Description |
|---|---|
mfa.enrolled | User enrolled a new MFA method |
mfa.unenrolled | User removed an MFA method |
mfa.verified | Successful MFA verification |
mfa.failed | Failed MFA verification attempt |
mfa.bypass_used | Admin bypass code was used |
mfa.policy_updated | Org MFA policy was changed |
mfa.enrollment_reset | Admin reset user MFA enrollment |
Search for MFA events via the audit log API:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.example.com/api/admin/audit-logs?action_prefix=mfa.&limit=50"MFA Prometheus Metrics
Section titled “MFA Prometheus Metrics”# MFA verification success raterate(arbitex_mfa_verifications_total{status="success"}[5m])/ rate(arbitex_mfa_verifications_total[5m])
# Failed MFA attempts (potential brute force)rate(arbitex_mfa_verifications_total{status="failed"}[5m])
# Un-enrolled user logins (while policy is optional)rate(arbitex_logins_total{mfa_enrolled="false"}[5m])Configure an alert for excessive MFA failures:
- alert: MFABruteForceAttempt expr: rate(arbitex_mfa_verifications_total{status="failed"}[5m]) > 10 for: 2m labels: severity: critical team: platform annotations: summary: "High MFA failure rate — possible brute force" description: "MFA failure rate is {{ $value }}/s — investigate for brute force activity."Middleware Implementation Reference
Section titled “Middleware Implementation Reference”The MFA enforcement middleware (backend/app/middleware/mfa_enforcement.py) is pure ASGI and runs on every HTTP request.
Sensitive Endpoint Prefixes
Section titled “Sensitive Endpoint Prefixes”The middleware enforces MFA step-up on any path that begins with one of the following prefixes:
"/api/admin""/api/keys""/api/saml-admin""/api/policy""/api/auth/mfa/setup""/api/auth/mfa/disable"Exempt Paths
Section titled “Exempt Paths”The following paths always pass through regardless of enforcement level:
# Exact matches"/health""/ready""/api/health""/api/auth/login""/api/auth/register""/api/auth/mfa/verify"
# Prefix match"/.well-known/"HTTP 403 Response Format
Section titled “HTTP 403 Response Format”When MFA is required but mfa_verified is absent or false, the middleware returns:
HTTP/1.1 403 ForbiddenContent-Type: application/jsonX-MFA-Required: true
{ "detail": "MFA verification required to access this resource", "mfa_required": true}Clients should check for the X-MFA-Required: true response header and redirect to the MFA verification flow rather than displaying the raw 403 error to users.
Policy Cache
Section titled “Policy Cache”Enforcement levels are cached in memory per org with a 60-second TTL:
Cache scope: per-process in-memory (dict, thread-safe via Lock)Cache key: org_id (UUID string)TTL: 60 secondsEviction: lazy — checked on each read, not actively expiredIn a multi-instance deployment, each process has its own cache and will converge to the new policy independently within one TTL cycle of the change.
JWT Claim
Section titled “JWT Claim”The middleware reads the standard HS256 user token from Authorization: Bearer. It checks for mfa_verified: true in the JWT payload. RS256 OAuth M2M tokens are not checked for mfa_verified — any decode failure (including RS256 key mismatch) causes fail-open pass-through.
The org ID is resolved in this order:
scope["state"]["org_id"](set by auth middleware)scope["state"]["tenant_id"]- JWT
tenant_idclaim - JWT
org_idclaim
If no org ID can be resolved, the request passes through (enforcement cannot apply without org context).