Files
cameleer-saas/docs/superpowers/specs/2026-04-26-server-mfa-handoff.md
hsiegeln bcb8a040f4
Some checks failed
CI / build (push) Failing after 38s
CI / docker (push) Has been skipped
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>
2026-04-26 14:07:27 +02:00

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

  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