MFA enrollment and enforcement in server (deferred from auth harmonization) #154

Open
opened 2026-04-26 17:16:27 +02:00 by claude · 0 comments
Owner

Deferred from the 2026-04-26 auth harmonization spec. Captured here so it isn't lost.

Background

The cameleer-saas team shipped per-tenant MFA in 2026-04-26 (cameleer-saas/docs/superpowers/specs/2026-04-26-password-reset-mfa-design.md) and produced a handoff doc for the server (2026-04-26-server-mfa-handoff.md). The handoff specified:

  • Server-side MFA enforcement: read mfa_enrolled claim from JWT, query GET /platform/api/tenant/{slug}/mfa-policy (5 min cache), return 403 with X-Cameleer-Error: APP_MFA_REQUIRED on enforce-but-not-enrolled.
  • Server-side MFA enrollment UI: TOTP + backup codes via direct calls to Logto's Management API.

This was deferred during the harmonization spec because it would:

  1. Duplicate the enrollment UI already shipped in the SaaS tenant portal.
  2. Introduce Logto Management API as a hard dependency in the server (couples a generic OIDC relying party to a specific IdP).
  3. Require Logto M2M credentials in every provisioned tenant server.

The access model means tenant owners can use the existing SaaS portal MFA UI, but operators and viewers — who skip the portal and land directly on the server dashboard — have no enrollment surface at all. So enforcement was deferred until enrollment for these users is solved.

Scope when this is picked up

Either of:

(A) IdP-agnostic redirect model — server detects APP_MFA_REQUIRED, redirects to a configurable enrollment URL. SaaS adds an /me/security self-service page accessible to all org members (operator/viewer included), keeping /tenant portal-admin-only. Server stays Logto-agnostic.

(B) Server-hosted enrollment per the handoff doc — server gets a "Profile / Security" page calling Logto Management API directly. Requires SaaS provisioner to inject CAMELEER_SERVER_LOGTO_M2M_CLIENTID / _CLIENTSECRET. Couples the server to Logto.

Recommend (A) — keeps the server's identity story clean ("we consume tokens, we don't manage credentials") and matches how standalone-with-Keycloak / Auth0 already works (MFA is the IdP's job).

Server work when picked up (assuming A)

  • New env var CAMELEER_SERVER_SAAS_PLATFORMURL — base for enrollment redirect URL (and the mfa-policy callback).
  • New env var or DB column for mfaEnrollmentUrl template (per-deployment override).
  • MfaEnforcementFilter — Spring filter, reads mfa_enrolled from JWT, caches tenant policy from GET /platform/api/tenant/{slug}/mfa-policy (5 min TTL), returns the APP_MFA_REQUIRED 403 envelope.
  • Frontend interceptor in ui/src/api/client.ts that recognises the X-Cameleer-Error: APP_MFA_REQUIRED header and redirects to the configured enrollment URL.
  • Exempt paths: /api/v1/health, /api/v1/auth/**, public assets, the enrollment-redirect handler itself.

Out of scope (separate issues)

  • Local-account TOTP (built-in users / env-var admin)
  • Local-account password reset (would need SMTP)

References

  • cameleer-saas/docs/superpowers/specs/2026-04-26-password-reset-mfa-design.md
  • cameleer-saas/docs/superpowers/specs/2026-04-26-server-mfa-handoff.md
  • cameleer-server/docs/superpowers/specs/2026-04-26-auth-harmonization-design.md (the harmonization spec that deferred this)
Deferred from the 2026-04-26 auth harmonization spec. Captured here so it isn't lost. ## Background The cameleer-saas team shipped per-tenant MFA in 2026-04-26 (`cameleer-saas/docs/superpowers/specs/2026-04-26-password-reset-mfa-design.md`) and produced a handoff doc for the server (`2026-04-26-server-mfa-handoff.md`). The handoff specified: - Server-side MFA enforcement: read `mfa_enrolled` claim from JWT, query `GET /platform/api/tenant/{slug}/mfa-policy` (5 min cache), return `403` with `X-Cameleer-Error: APP_MFA_REQUIRED` on enforce-but-not-enrolled. - Server-side MFA enrollment UI: TOTP + backup codes via direct calls to Logto's Management API. This was deferred during the harmonization spec because it would: 1. Duplicate the enrollment UI already shipped in the SaaS tenant portal. 2. Introduce Logto Management API as a hard dependency in the server (couples a generic OIDC relying party to a specific IdP). 3. Require Logto M2M credentials in every provisioned tenant server. The access model means tenant `owner`s can use the existing SaaS portal MFA UI, but `operator`s and `viewer`s — who skip the portal and land directly on the server dashboard — have no enrollment surface at all. So enforcement was deferred until enrollment for these users is solved. ## Scope when this is picked up Either of: **(A) IdP-agnostic redirect model** — server detects `APP_MFA_REQUIRED`, redirects to a configurable enrollment URL. SaaS adds an `/me/security` self-service page accessible to all org members (operator/viewer included), keeping `/tenant` portal-admin-only. Server stays Logto-agnostic. **(B) Server-hosted enrollment per the handoff doc** — server gets a "Profile / Security" page calling Logto Management API directly. Requires SaaS provisioner to inject `CAMELEER_SERVER_LOGTO_M2M_CLIENTID` / `_CLIENTSECRET`. Couples the server to Logto. Recommend (A) — keeps the server's identity story clean ("we consume tokens, we don't manage credentials") and matches how standalone-with-Keycloak / Auth0 already works (MFA is the IdP's job). ## Server work when picked up (assuming A) - New env var `CAMELEER_SERVER_SAAS_PLATFORMURL` — base for enrollment redirect URL (and the mfa-policy callback). - New env var or DB column for `mfaEnrollmentUrl` template (per-deployment override). - `MfaEnforcementFilter` — Spring filter, reads `mfa_enrolled` from JWT, caches tenant policy from `GET /platform/api/tenant/{slug}/mfa-policy` (5 min TTL), returns the `APP_MFA_REQUIRED` 403 envelope. - Frontend interceptor in `ui/src/api/client.ts` that recognises the `X-Cameleer-Error: APP_MFA_REQUIRED` header and redirects to the configured enrollment URL. - Exempt paths: `/api/v1/health`, `/api/v1/auth/**`, public assets, the enrollment-redirect handler itself. ## Out of scope (separate issues) - Local-account TOTP (built-in users / env-var admin) - Local-account password reset (would need SMTP) ## References - `cameleer-saas/docs/superpowers/specs/2026-04-26-password-reset-mfa-design.md` - `cameleer-saas/docs/superpowers/specs/2026-04-26-server-mfa-handoff.md` - `cameleer-server/docs/superpowers/specs/2026-04-26-auth-harmonization-design.md` (the harmonization spec that deferred this)
Sign in to join this conversation.