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
- Enforce 2FA for a role (
two_factor_enforced_roles) and give a user that role, with 2FA not yet set up.
- Have that user reach the CP on a session that was never elevated (e.g. restored from the remember cookie).
- The setup page appears; click "Set up".
- 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.
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:
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 tolaravel.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_tokenand reached the CP without submitting the login form — his sessions (read straight from thesessionstable) contained nostatamic_elevated_sessionkey 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.
DisableTwoFactorAuthenticationis only visible whenhasEnabledTwoFactorAuthentication()is true, which requires bothtwo_factor_secretandtwo_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
two_factor_enforced_roles) and give a user that role, with 2FA not yet set up.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
SESSION_DRIVER=databaseelevated_sessions_enabledtrue,elevated_session_duration15Possibly related: #15212 — a different CP dialog (passkey creation) that also spins indefinitely instead of reporting its failure.