New Issue Checklist
Issue Description
Logging in with MFA (OTP) enabled fails with:
CSRF token validation failed. Please refresh the page and try again.
The login returns HTTP 403 with EBADCSRFTOKEN. The password step works; the failure happens on the OTP step. It reproduces on a single dashboard instance, in incognito, with no browser extensions, so it is not the multi-replica / sticky-session scenario from #3015.
Steps to reproduce
- Configure a dashboard user with
mfa set.
- Open the login page, enter username + password, then the one-time password, and submit.
- Login fails with "CSRF token validation failed".
Actual Outcome
The OTP step issues two POST /login requests. The first authenticates successfully; Passport's req.logIn then calls req.session.regenerate() (session-fixation protection), which destroys the session that holds the CSRF token (csrf-sync stores the token in req.session). The browser cancels that first response and never receives the regenerated session cookie, so the second request lands on the now-destroyed session, finds no CSRF token, and fails validation with 403.
The duplicate submission originates in LoginForm.react.js: the submit control is type="submit" (which natively submits the form) and its onClick also calls this.formRef.current.submit(), producing a second, redundant submission. On the password step both submissions return 302, so it is harmless; only the MFA step exposes it because the first POST regenerates the session.
Expected Outcome
Login with MFA succeeds; the form is submitted exactly once.
Environment
- Parse Dashboard version:
9.1.1 (also present on alpha / HEAD)
- MFA: enabled (OTP)
- Deployment: single instance (not multi-replica)
New Issue Checklist
Issue Description
Logging in with MFA (OTP) enabled fails with:
The login returns HTTP 403 with
EBADCSRFTOKEN. The password step works; the failure happens on the OTP step. It reproduces on a single dashboard instance, in incognito, with no browser extensions, so it is not the multi-replica / sticky-session scenario from #3015.Steps to reproduce
mfaset.Actual Outcome
The OTP step issues two
POST /loginrequests. The first authenticates successfully; Passport'sreq.logInthen callsreq.session.regenerate()(session-fixation protection), which destroys the session that holds the CSRF token (csrf-syncstores the token inreq.session). The browser cancels that first response and never receives the regenerated session cookie, so the second request lands on the now-destroyed session, finds no CSRF token, and fails validation with 403.The duplicate submission originates in
LoginForm.react.js: the submit control istype="submit"(which natively submits the form) and itsonClickalso callsthis.formRef.current.submit(), producing a second, redundant submission. On the password step both submissions return 302, so it is harmless; only the MFA step exposes it because the first POST regenerates the session.Expected Outcome
Login with MFA succeeds; the form is submitted exactly once.
Environment
9.1.1(also present onalpha/ HEAD)