docs: add MFA handoff document for cameleer-server team
Some checks failed
CI / build (push) Failing after 38s
CI / docker (push) Has been skipped

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>
This commit is contained in:
hsiegeln
2026-04-26 14:07:27 +02:00
parent d52084a081
commit bcb8a040f4

View File

@@ -0,0 +1,152 @@
# 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:**
```json
{
"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
1. User clicks "Set up MFA" in settings
2. Show QR code (200x200px) with the TOTP secret URI
3. User scans with authenticator app
4. User enters 6-digit verification code
5. On success -> show 10 backup codes in a 2-column monospace grid
6. "Copy all" and "Download .txt" buttons
7. Checkbox: "I've saved my backup codes" — must be checked before dismissing
8. After dismissal, force token refresh to get `mfa_enrolled: true` in 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 |