Covers JWT mfa_enrolled claim, enforcement model (APP_MFA_REQUIRED), Logto Management API contract for TOTP enrollment and backup codes, UX requirements, and error states. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.4 KiB
Cameleer-Server MFA Handoff Document
Date: 2026-04-26 For: cameleer-server team Context: The SaaS platform now supports TOTP MFA with backup codes. This document specifies what the server team needs to implement for MFA enrollment in the server UI.
1. JWT Claim: mfa_enrolled
Every access token now includes an mfa_enrolled: boolean claim, set by the Logto Custom JWT script. The server already parses JWT claims for the roles field — mfa_enrolled works identically.
Example decoded JWT payload:
{
"sub": "user-id-123",
"roles": ["server:admin"],
"mfa_enrolled": true,
"aud": "https://api.cameleer.local",
"scope": "tenant:manage tenant:view"
}
2. Enforcement
When to enforce
Check whether the tenant requires MFA:
- Endpoint:
GET /platform/api/tenant/{slug}/mfa-policy - Auth: M2M token (same as existing server -> SaaS API calls)
- Response:
{ "mfaRequired": true/false } - Cache: 5-minute TTL recommended
How to enforce
On authenticated requests, if mfaRequired is true and the JWT mfa_enrolled claim is false:
Response:
HTTP 403
X-Cameleer-Error: APP_MFA_REQUIRED
Content-Type: application/json
{
"error": "APP_MFA_REQUIRED",
"code": "mfa_enrollment_required",
"message": "Your organization requires multi-factor authentication"
}
Exempt paths: MFA enrollment endpoints (below), health checks, public assets.
The server UI should intercept 403 responses with X-Cameleer-Error: APP_MFA_REQUIRED and redirect to the MFA enrollment page.
3. MFA Enrollment API
The server needs to call Logto's Management API to manage MFA for users. Use the existing M2M token for authentication.
Get MFA status
GET https://{logto-endpoint}/api/users/{userId}/mfa-verifications
Authorization: Bearer {m2m_token}
Response: [
{ "id": "ver-123", "type": "Totp", "createdAt": "..." },
{ "id": "ver-456", "type": "BackupCode", "createdAt": "..." }
]
Generate TOTP secret
Generate a 20-byte random secret, Base32-encode it, and create a QR code URI:
otpauth://totp/Cameleer:{userEmail}?secret={base32Secret}&issuer=Cameleer
Show the QR code to the user. After they scan and provide a 6-digit code, verify it server-side using the TOTP algorithm (RFC 6238, HMAC-SHA1, 30-second window, +/-1 step drift).
Bind TOTP to user
After successful verification:
POST https://{logto-endpoint}/api/users/{userId}/mfa-verifications
Authorization: Bearer {m2m_token}
Content-Type: application/json
{ "type": "Totp", "secret": "{base32Secret}" }
Response: { "type": "Totp", "secret": "...", "secretQrCode": "..." }
Generate backup codes
After TOTP is bound:
POST https://{logto-endpoint}/api/users/{userId}/mfa-verifications
Authorization: Bearer {m2m_token}
Content-Type: application/json
{ "type": "BackupCode" }
Response: { "type": "BackupCode", "codes": ["abc123", "def456", ...] }
Display the 10 codes once. User must acknowledge saving them before dismissing.
Remove MFA (admin action)
DELETE https://{logto-endpoint}/api/users/{userId}/mfa-verifications/{verificationId}
Authorization: Bearer {m2m_token}
Remove all verifications (TOTP + BackupCode) to fully reset MFA for a user.
4. UX Requirements
Enrollment flow
- User clicks "Set up MFA" in settings
- Show QR code (200x200px) with the TOTP secret URI
- User scans with authenticator app
- User enters 6-digit verification code
- On success -> show 10 backup codes in a 2-column monospace grid
- "Copy all" and "Download .txt" buttons
- Checkbox: "I've saved my backup codes" — must be checked before dismissing
- After dismissal, force token refresh to get
mfa_enrolled: truein JWT
Enrolled state
- Show "Authenticator app configured" with green status badge
- "Regenerate backup codes" button
- "Remove MFA" button with confirmation dialog
Backup code fallback (sign-in)
This is handled by the SaaS custom sign-in UI, not the server. No server changes needed for the sign-in flow.
5. Error States
| Scenario | Response |
|---|---|
| User already has TOTP enrolled | 422 — "TOTP already configured" |
| Invalid TOTP code during setup | Show error, let user retry |
| Backup code already used (sign-in) | Handled by SaaS sign-in UI |
| All backup codes exhausted | Admin removes MFA via team page |
| Remove MFA while enforcement active | User will be prompted to re-enroll on next request |