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>
153 lines
4.4 KiB
Markdown
153 lines
4.4 KiB
Markdown
# 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 |
|