#20462·phpmyadmin

[Bug]: Unconditional Session::secure() before login form invalidates session on every GET, causing MismatchedSessionId in browsers

Author: arturod67Created Aug 31, 2026Updated Aug 31, 2026
LabelsBug

AuthenticationPlugin::authenticate() calls Session::secure() unconditionally before every rendering of the login form:

php
/* Show login form (this exits) */
if (! $success) {
    /* Force generating of new session */
    Session::secure();

Session::secure() calls session_regenerate_id(true), so every unauthenticated GET invalidates the previous session. The login form contains a hidden set_session field bound to the session that rendered it. If any other request reaches the server between rendering and submitting the form, the session cookie is replaced and the POST fails with:

PhpMyAdmin\Exceptions\MismatchedSessionId: Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.

Requests that trigger this in real browsers but not in curl:

  • speculative prefetch/prerender from the address bar (Chrome/Edge default)
  • the localized messages endpoint (index.php?route=/messages&l=...) loaded by the login page itself (moved into the routing/auth stack by #17677)
  • any parallel tab or duplicated request

I believe this explains the long tail of hard-to-reproduce reports of this error message, e.g. #19015 (fails on Chrome/Edge, works on Firefox unless DevTools is open, which changes request behavior) and possibly part of #16922.

To Reproduce

Server-side only, no browser needed:

bash
curl -sS -c /tmp/j.jar https://HOST/ -o /tmp/1.html
curl -sS -b /tmp/j.jar -c /tmp/j.jar https://HOST/ -o /tmp/2.html
grep -o 'name="set_session" value="[^"]*"' /tmp/1.html /tmp/2.html

The two set_session values differ: each GET of the login form destroys the previous session. Submitting the form rendered by request 1 after request 2 happened reproduces the error 100% of the time. Session storage itself is fine (verified: a plain PHP session with the same cookie name persists across requests on the same setup).

Expected behavior

Loading the login form multiple times should not invalidate the session, so that the form rendered by any of those requests can still be submitted.

Proposed fix

Session fixation protection is already provided by the Session::secure() call on successful login (AuthenticationCookie::authenticate()). Regenerating on every form render is redundant and breaks multi-request scenarios. Only generating when no token exists yet fixes it:

php
if (! $success) {
    /* Generate session token if missing; regeneration happens on successful login */
    if (Session::getToken() === '') {
        Session::secure();
    }

After this change the two curl requests above return identical set_session values and browser login works reliably. Logout/re-login still rotates the session id (verified).

Fix commit on my fork: https://github.com/YorkHost-fr/phpmyadmin/commit/d49735e

Happy to open a PR if this approach is acceptable.

Server configuration

  • Operating system: Debian 13 (trixie)
  • Web server: nginx + PHP-FPM
  • PHP version: 8.4.24
  • phpMyAdmin version: 6.0.0-dev (current master; verified the code path is identical to master)

Client configuration

  • Browser: reproduced on Chromium-based and others; also reproduced entirely server-side with curl (see above)

How to Reproduce

No response

Expected behavior

No response

Screenshots

No response

Operating System

No response

Web Server

No response

Database Server

No response

Database version

No response

PHP version

No response

phpMyAdmin version

6.0.0-dev

browser

No response

Additional context

No response