You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Enforced 2FA setup dead-ends when the session is not elevated — dialog swallows the 403 #15216
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 bothtwo_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.
Hey, we've just hit this on 6.33.0 with a brand new editor, so a "me too" plus one detail that might be worth folding into #15329.
Our trigger is simpler than the remember cookie, and probably a lot more common: the elevated session just expiring during first-time setup. Straight from our production logs:
14:45:23 user logs in, session elevated
14:45:25 POST /cp/two-factor/enable → 200, QR shown
15:00:37 POST /cp/two-factor/confirm → 403, fourteen seconds after the fifteen minute elevation lapsed
everything after that → 403
All he was doing in between was installing an authenticator app on his phone and scanning the code. With two_factor_enforced_roles set to ['*'], anyone slow on their first setup lands in the same place. A second invitee ran the same flow that afternoon, finished in four minutes, and was fine.
Once you're in it, it's a hard dead end exactly as described above. GET /cp/elevated-session and GET /cp/auth/confirm-password both 302 back to /cp/auth/two-factor-setup, so nothing in the browser can re-elevate. Logging out and back in is the only way through, and nobody guesses that.
One thing I think #15329 may still leave open: it fixes getSetupCode() but not confirm(). The PR guarantees a fresh elevation when the dialog opens, but if the user then spends longer than elevated_session_duration scanning the QR before typing their code, the confirm POST still 403s, and
throws a TypeError reading errors.code on {"message":"Requires an elevated session."}. So they get no feedback at all. Ours submitted six times in two seconds and saw nothing happen, which is why it reads as a hang rather than a rejection. The same optional chaining there, or re-checking elevation before confirm, would close it off.
Any idea when #15329 might get a look? It's been open since 1 September, and in this state a newly invited user is locked out of the CP entirely with no way back.
Activity
Fix enforced two-factor setup without elevation
revans-premier-education commentedon Sep 22, 2026
Hey, we've just hit this on 6.33.0 with a brand new editor, so a "me too" plus one detail that might be worth folding into #15329.
Our trigger is simpler than the remember cookie, and probably a lot more common: the elevated session just expiring during first-time setup. Straight from our production logs:
POST /cp/two-factor/enable→ 200, QR shownPOST /cp/two-factor/confirm→ 403, fourteen seconds after the fifteen minute elevation lapsedAll he was doing in between was installing an authenticator app on his phone and scanning the code. With
two_factor_enforced_rolesset to['*'], anyone slow on their first setup lands in the same place. A second invitee ran the same flow that afternoon, finished in four minutes, and was fine.Once you're in it, it's a hard dead end exactly as described above.
GET /cp/elevated-sessionandGET /cp/auth/confirm-passwordboth 302 back to/cp/auth/two-factor-setup, so nothing in the browser can re-elevate. Logging out and back in is the only way through, and nobody guesses that.One thing I think #15329 may still leave open: it fixes
getSetupCode()but notconfirm(). The PR guarantees a fresh elevation when the dialog opens, but if the user then spends longer thanelevated_session_durationscanning the QR before typing their code, the confirm POST still 403s, andthrows a TypeError reading
errors.codeon{"message":"Requires an elevated session."}. So they get no feedback at all. Ours submitted six times in two seconds and saw nothing happen, which is why it reads as a hang rather than a rejection. The same optional chaining there, or re-checking elevation before confirm, would close it off.Environment: Statamic 6.33.0 Pro, Laravel 13.32, PHP 8.5, eloquent user driver,
SESSION_DRIVER=database,elevated_sessions_enabledtrue,elevated_session_duration15.Any idea when #15329 might get a look? It's been open since 1 September, and in this state a newly invited user is locked out of the CP entirely with no way back.