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.
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:WordPress-iOS/WordPress/Classes/Utility/WebViewController/CookieJar.swift
Lines 156 to 159 in 3db3728
WordPress sets the cookie value to
username|expiration|token|hmacand PHP'ssetcookie()URL-encodes it, so the check relies on the first|arriving as%7C. An encoded character inside the username breaks that:adminadmin%7C…adminuser@example.comuser%40example.com%7C…userjohn doejohn+doe%7C…john+doeImpact
AuthenticationService.loadAuthCookiesForSelfHostedtreats a failed check as "no cookie" and POSTs the username and password towp-login.phpagain:WordPress-iOS/WordPress/Classes/Services/AuthenticationService.swift
Lines 31 to 40 in 3db3728
This affects sites that
RequestAuthenticatorauthenticates with.siteLogincredentials. 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 returnsfalseforuser@example.comandjohn 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
urlencodealso turns spaces into+— and split on|, asisWordPressLoggedInAtomic(username:)already does.