--- id: KEY-WP-0008 type: workplan title: "Registration handoff and client-aware MFA policy" domain: infotech repo: key-cape status: finished owner: grok topic_slug: netkingdom created: "2026-08-09" updated: "2026-08-16" depends_on: - NK-WP-0025 state_hub_workstream_id: "23eb67d4-cf10-57a1-92f6-90146a294bda" --- # KEY-WP-0008 - registration handoff and client-aware MFA Let a registered OIDC client offer NetKingdom signup and request step-up without making KeyCape an account store or weakening high-assurance clients. ## T01 - Add a safe registration handoff ```task id: KEY-WP-0008-T01 status: done priority: high state_hub_task_id: "e5fb4d73-1e19-565c-80d5-e0eee274ce63" ``` Define an allow-listed registration URL for eligible clients and preserve client ID, redirect URI, PKCE intent, tenant hint, and return context in a signed, expiring state envelope. Registration completion must restart the normal authorization flow and must not mint a token directly. Done when unknown users can choose signup from an eligible authorization flow without open redirect, client substitution, or state replay. Implemented `prompt=create` and `/authorize/register` against the client's static `registrationUrl`, plus HMAC-signed `kc_handoff` envelopes consumed once at `/authorize/return`. Return restarts `/authorize` and never mints a code. Ineligible clients get no signup link. Live registration entry remains user-engine-owned per NK-WP-0025; KeyCape only issues the return envelope. ## T02 - Replace global MFA with client-aware minimum assurance ```task id: KEY-WP-0008-T02 status: done priority: high state_hub_task_id: "1ba50454-b6f5-578e-a526-da74b8082a89" ``` Replace the single require-for-all switch with policy that combines client minimum assurance, requested ACR/step-up, tenant policy, protected action, and current session assurance. Preserve mandatory MFA for platform/admin clients. Allow coulomb-social ordinary login at password assurance when no stronger rule applies. Done when one low-assurance client cannot suppress MFA for another client or reuse an under-assured session for a high-assurance request. Implemented with nullable per-client `mfaRequired`: an explicit client value overrides the provider default only for that client. Absent values preserve the existing provider-driven policy. 2026-08-16: `DecideAssurance` now combines client minimum, requested ACR, provider default, `max_age`, `prompt=login`, and current KeyCape login-session level. An AAL1 session cannot satisfy an AAL2 client or `acr_values=aal2`. `coulomb-social` in `config/dev-config.yaml` is `mfaRequired: false`; other clients keep the provider default. Users without an enrolled factor are sent to the client's `enrollmentUrl` instead of completing authorization. ## T03 - Support explicit step-up and fresh authentication ```task id: KEY-WP-0008-T03 status: done priority: high state_hub_task_id: "f7acbc12-e597-5d1e-8b29-bfa02237730f" ``` Implement supported ACR/max-age or equivalent IAM Profile parameters, invoke privacyIDEA only when policy requires it, and return verifiable assurance claims. Handle users without an enrolled factor through a safe enrollment handoff rather than an authorization bypass. Done when coulomb.social can request MFA for a profile/action and verify the result from token claims. Implemented `acr_values` preservation and AAL2/MFA forcing through the privacyIDEA challenge. The existing token `assurance` claim reports `aal2` and `mfa: true` only after successful verification. ## T04 - Prove policy isolation and compatibility ```task id: KEY-WP-0008-T04 status: done priority: high state_hub_task_id: "bc660767-387e-59a5-bfce-caccf4082c08" ``` Test known and unknown users, registration link eligibility, state expiry and replay, password-only coulomb-social login, profile/action step-up, no-factor enrollment, OpenBao mandatory MFA, cross-client session reuse, and logout. Keep static client registration and exact redirect rules unchanged. Done when existing high-assurance clients pass unchanged and the new coulomb-social journey passes live. 2026-08-16: isolation tests cover known/unknown users, registration eligibility, state expiry/replay, password-only coulomb-social, ACR step-up, no-factor enrollment handoff, OpenBao mandatory MFA, cross-client AAL1 session reuse, logout, and exact redirect enforcement. Full Go suite passes. Live coulomb-social AAL1/AAL2 isolation was already proven on railiance01 under NK-WP-0025-T05 (2026-08-14); this closeout adds the KeyCape-side regression suite and `/logout`.