Skip to content

Enforced 2FA setup dead-ends when the session is not elevated — dialog swallows the 403 #15216

Description

@iLord-DEV

Bug description

When two-factor authentication is enforced for a role, the setup dialog can end in a dead loop: it opens, shows a spinner, and never loads its content.

The cause is visible in the network tab:

POST /cp/two-factor/enable
403 Forbidden
{"message":"Requires an elevated session."}

The dialog fetches its content from that endpoint, which sits behind RequireElevatedSession. When the session carries no elevation, the 403 is never surfaced — the spinner just keeps turning. Nothing is written to laravel.log, so from the outside it looks like a hang rather than a denied request.

The user is stuck: the enforcement middleware redirects every CP request to the setup page, and the setup page cannot complete.

Why the session had no elevation

Statamic elevates a session in five places: the form login (HandlesLogins::authenticate()), the two-factor challenge, passkey login, OAuth, and the elevated-session dialog itself.

A session restored from the remember cookie passes through none of them. In our case the affected user had a remember_token and reached the CP without submitting the login form — his sessions (read straight from the sessions table) contained no statamic_elevated_session key at all.

I could not reproduce the cookie handshake in a test, so treat that part as the likely trigger rather than a proven one. The reported bug is independent of it: whatever causes the missing elevation, a 403 from this endpoint should be shown, not swallowed.

Second-order problem

Once the user is in this state, an admin cannot clean it up from the CP either. DisableTwoFactorAuthentication is only visible when hasEnabledTwoFactorAuthentication() is true, which requires both two_factor_secret and two_factor_confirmed_at. A user who started setup but never confirmed has a secret and no confirmation — so the action does not appear, and the half-finished state can only be cleared in the database.

Steps to reproduce

  1. Enforce 2FA for a role (two_factor_enforced_roles) and give a user that role, with 2FA not yet set up.
  2. Have that user reach the CP on a session that was never elevated (e.g. restored from the remember cookie).
  3. The setup page appears; click "Set up".
  4. The dialog opens and spins forever. The network tab shows the 403 above.

Expected

The dialog surfaces the error and offers the elevated-session confirmation (password prompt), so the user can proceed — or the setup flow requests elevation itself before calling the endpoint.

Environment

  • Statamic 6.27.2 (Pro)
  • Laravel 13.25, PHP 8.5
  • Eloquent user driver, SESSION_DRIVER=database
  • elevated_sessions_enabled true, elevated_session_duration 15
  • Reproduced in both Safari and Chrome

Possibly related: #15212 — a different CP dialog (passkey creation) that also spins indefinitely instead of reporting its failure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions