Skip to main content
Version: Next

MFA Self-Service Portal

The integrated self-service portal lets a user authenticate and manage their own MFA factors. The entry route is:

/mfa/register/home
/mfa/register/home/{languageTag}

The localized route keeps the selected language throughout login, enrollment, device management, and recovery-code pages. Do not send users directly to /login: a normal login page requires a valid OIDC, SAML, device, or internal self-service flow.

User journey

An anonymous portal entry creates a fresh canonical session anchor and a typed internal login flow with a bounded lifetime. A successful primary login rotates the browser envelope, publishes the authenticated identity and selected backend reference server-side, consumes the one-shot login flow, and redirects to the protected portal. The browser cookie is an authenticated opaque reference; it does not contain identity, factors, credentials, continuation targets, or ceremony state.

Available operations

AreaUser operationMain routes
TOTPEnroll a TOTP secret; delete the current TOTP factor.GET/POST /mfa/totp/register, DELETE /mfa/totp
WebAuthnRegister a passkey/security key; list, rename, or delete credentials./mfa/webauthn/register*, GET /mfa/webauthn/devices, POST .../name, DELETE .../device/{id}
Recovery codesEnroll, save, or replace recovery codes./mfa/recovery/register*, POST /mfa/recovery/generate

Every route also has a localized /{languageTag} form. The UI carries opaque flow and ceremony tickets in local query parameters where required. Operators and applications must not create, rewrite, persist, or interpret those values.

Recovery codes are one-time credentials. Replacement invalidates the old set. At least one TOTP or WebAuthn factor must remain active before recovery codes can be generated. Users should save new codes through the supplied page before continuing; the server never exposes stored recovery-code hashes.

Enrollment started by an application

OIDC clients and SAML service providers can require factor enrollment with require_mfa. After successful login or step-up, Nauthilus checks each required method and sends the user through the missing registrations in order:

/mfa/totp/register
/mfa/webauthn/register
/mfa/recovery/register
/mfa/register/continue

require_mfa is enrollment policy. supported_mfa controls methods offered for a challenge, while required_mfa_level controls the strength of the fresh proof. A registered high-assurance factor does not satisfy an assurance gate until the user actually completes it, and a strong proof does not invent another required enrollment.

See MFA Assurance Policy for the configuration model.

Step-up for sensitive portal changes

Sensitive portal mutations do not reuse an arbitrary fresh OIDC or SAML proof. Nauthilus requires assurance with the exact self-service scope and at least level 1.

When that proof is absent or stale, Nauthilus stores one typed operation bound to the current session and identity, then redirects the browser to /login/mfa?flow=opaque-ticket. The challenge offers only factors proven available from the selected backend. Successful TOTP, WebAuthn, or recovery verification commits self-service-scoped assurance and returns to the bound mutation surface.

The stored operation contains only bounded server-owned data needed to finish the action. For example, a WebAuthn rename binds the credential reference and new device name to the step-up record and uses /mfa/self-service/continue after assurance. A user-controlled return URL is never accepted.

Retry behavior

Retry semantics depend on the operation:

  • An invalid TOTP enrollment code keeps the pending secret and operation available for a corrected code. It does not create a second backend enrollment.
  • Repeating a successful TOTP enrollment submission resumes the required-MFA chain idempotently instead of registering the factor twice.
  • A wrong TOTP or recovery-code challenge renders a new bounded form and preserves the typed step-up binding for another attempt. The browser must use the newly issued CSRF token.
  • WebAuthn assertions and attestations are single-use. A missing, expired, kind-mismatched, or binding-mismatched ceremony fails closed. Restart with the matching begin route; never replay the rejected browser payload.
  • A completed step-up ticket is single-use. Replaying it, changing its operation, or presenting it from another session fails closed.
  • A backend mutation uses a server-generated operation identity. If the outcome is uncertain, reconcile the selected backend before asking the user to repeat the mutation.

Expired and missing sessions

The portal entry is restartable; protected continuations are not.

  • Visiting /mfa/register/home[/languageTag] without a usable session starts a new bounded self-service login flow and redirects to the matching localized login page.
  • Visiting another protected self-service route without a live authenticated canonical session returns 401 with the self_service_session_expired page. The user must sign in again through the portal entry.
  • An expired or invalid flow/step-up/ceremony ticket is not reconstructed from browser input. Start again from the nearest safe entry point.
  • Login completion rotates the browser session. The pre-authentication cookie and consumed flow cannot be reused.
  • Logout and session revocation invalidate the server-side anchor and its indexed child records.

The portal's restart message may tell the user to reopen 2FA self-service from their application. Applications should link to the portal home, not preserve a prior flow or ceremony query string.

Distributed identity deployments

In split edge/authority mode, the edge hosts the browser flow while the authority owns credential persistence. The session keeps the final subject, factor identity, backend name, and opaque backend reference selected during authentication. Enrollment and self-service mutations must use that same authority and backend; they must not select a new backend from browser input.

The edge and authority must run compatible canonical session, API, and artifact generations. Avoid mixed rolling traffic between binaries that disagree on the envelope or typed store contract. Legacy browser-session records and retired WebAuthn ceremony cookies are not fallback inputs.

Operator verification

Exercise the real browser path through the deployment's reverse proxy:

  1. Open the localized portal home without cookies and verify a same-origin redirect to localized /login with an opaque flow ticket.
  2. Authenticate and verify session rotation, a 303 back to the portal, and the authenticated account view.
  3. Enroll TOTP with one wrong code followed by the correct code; confirm the secret remains retryable and is stored once.
  4. Register WebAuthn, then prove that replaying the finish request fails and a fresh begin succeeds.
  5. Generate and save recovery codes, use one for a step-up, and verify that it cannot be reused.
  6. Trigger a sensitive delete, rename, or replacement without fresh self-service assurance and verify the bound MFA selection/return path.
  7. Expire or revoke the session, confirm protected routes return the explicit expired-session page, then restart from portal home.
  8. In split mode, prove that every mutation reaches the originally selected authority/backend and survives neither cross-session nor cross-account ticket reuse.

Do not log passwords, TOTP secrets/codes, recovery codes, WebAuthn challenges/assertions, credential IDs, cookies, or opaque flow handles. Use bounded route/result classes and server-side correlation instead.