Skip to content

Self-hosted web view login cookie check fails for usernames containing @ or spaces #26115

Description

@jkmassel

Found while reviewing #26103. Pre-existing — not introduced by that PR, and unchanged by #26106 and #26104.

The logged-in cookie check for self-hosted sites never matches when the username contains a character that PHP URL-encodes, so the app logs in again before every authenticated web view load.

Root cause

HTTPCookie.isWordPressLoggedIn(username:) takes the username to be everything before the first % in the cookie value:

private func isWordPressLoggedIn(username: String) -> Bool {
name.hasPrefix(loggedInCookieName)
&& value.components(separatedBy: "%").first == username
}

WordPress sets the cookie value to username|expiration|token|hmac and PHP's setcookie() URL-encodes it, so the check relies on the first | arriving as %7C. An encoded character inside the username breaks that:

Username Cookie value Parsed username Match
admin admin%7C… admin ✅
user@example.com user%40example.com%7C… user ❌
john doe john+doe%7C… john+doe ❌

Impact

AuthenticationService.loadAuthCookiesForSelfHosted treats a failed check as "no cookie" and POSTs the username and password to wp-login.php again:

cookieJar.hasWordPressSelfHostedAuthCookie(for: loginURL, username: username) { hasCookie in
guard !hasCookie else {
success()
return
}
self.getAuthCookiesForSelfHosted(loginURL: loginURL, username: username, password: password, success: { cookies in
cookieJar.setCookies(cookies) {
success()
}

This affects sites that RequestAuthenticator authenticates with .siteLogin credentials. The login itself still succeeds, so the cost is an extra login round-trip — and a new session on the site — before each authenticated load, rather than a visible failure.

WordPress.com usernames are normally limited to lowercase letters and numbers, so the same predicate is not expected to misfire on the WordPress.com path.

Verification

Confirmed with a Foundation probe (macOS 27.0.1): HTTPCookie.cookies(withResponseHeaderFields:for:) keeps the value percent-encoded, and the predicate returns false for user@example.com and john doe.

The cookie values in the probe were built from WordPress's cookie format. We have not reproduced this against a live self-hosted site.

Suggested fix

Percent-decode the value before parsing — PHP's urlencode also turns spaces into + — and split on |, as isWordPressLoggedInAtomic(username:) already does.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions