docs: add MFA handoff document for cameleer-server team
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:
152
docs/superpowers/specs/2026-04-26-server-mfa-handoff.md
Normal file
152
docs/superpowers/specs/2026-04-26-server-mfa-handoff.md
Normal 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 |
|
||||
Reference in New Issue
Block a user