Opened 6 weeks ago
Last modified 3 weeks ago
#66000 new defect (bug)
Application Passwords warning persists after HTTP Basic Authentication is disabled
| Reported by: | ethicaladitya | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Application Passwords | Version: | |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: |
Description
I found an issue where WordPress continues to show the following message under Users → Profile → Application Passwords:
“Your website appears to use Basic Authentication, which is not currently compatible with Application Passwords.”
This happens even after HTTP Basic Authentication has been disabled.
The issue appears to be browser/session dependent.
## Steps to reproduce
- Enable HTTP Basic Authentication on a WordPress site.
- Open the site in Browser A and access the WordPress profile page.
- Go to Users → Profile → Application Passwords.
- The Basic Authentication warning is displayed.
- Disable HTTP Basic Authentication.
- Clear any available caches and resave permalinks.
- Return to the Application Passwords section in Browser A.
The warning continues to be displayed.
However, if I open the same site with the same WordPress user in Browser B, where the site was never accessed while HTTP Authentication was enabled, Application Passwords work normally.
The same happens in an incognito/private window.
### Example
- Browser A → accessed while HTTP Auth was enabled → HTTP Auth disabled → warning remains.
- Browser B → never accessed while HTTP Auth was enabled → Application Passwords work.
- Incognito → Application Passwords work.
## Additional checks
After disabling HTTP Authentication, I checked the server environment and neither of these variables was present:
`text
PHP_AUTH_USER
PHP_AUTH_PW
`
I also tried clearing caches, resaving permalinks.
The affected browser session still showed the warning.
## Expected behavior
Once HTTP Basic Authentication is disabled, WordPress should detect that it is no longer enabled and allow Application Passwords to be generated.
A browser session that previously encountered HTTP Basic Authentication should not continue preventing Application Passwords from being used.
## Actual behavior
The warning persists in the browser/session that previously encountered HTTP Basic Authentication, while a fresh browser session works correctly.
This makes the issue appear to be related to cached browser authentication/session state and how WordPress detects Basic Authentication.
## Environment
- WordPress 7.1
- Nginx
- PHP-FPM
- HTTP/3
## Workaround
Opening the site in a new/incognito browser allows Application Passwords to work.
Attachments (1)
Change History (4)
This ticket was mentioned in PR #13597 on WordPress/wordpress-develop by @aayushk777.
3 weeks ago
#3
- Keywords has-patch has-unit-tests added
Trac ticket: https://core.trac.wordpress.org/ticket/66000
The problem
wp_is_site_protected_by_basic_auth() treats the presence of PHP_AUTH_USER / PHP_AUTH_PW as proof that the server enforces HTTP Basic Auth:
$is_protected = ! empty( $_SERVER['PHP_AUTH_USER'] ) || ! empty( $_SERVER['PHP_AUTH_PW'] );
Under the CGI/FastCGI SAPI, PHP populates those keys from any Authorization: Basic header the client chooses to send — no server-side Basic Auth needs to be configured for them to appear. Browsers keep replaying cached credentials to an origin for the rest of the session, so after Basic Auth is switched off the affected browser keeps seeing:
Your website appears to use Basic Authentication, which is not currently compatible with Application Passwords.
while a fresh browser or private window works normally. That notice hides the form in wp-admin/user-edit.php and hard-fails wp-admin/authorize-application.php with a 501.
As noted on the ticket, the two cases are indistinguishable from within the request — AUTH_TYPE and REMOTE_USER are unset for both a genuinely protected site and a stale credential, so neither can discriminate.
The change
The distinction does exist on the wire: a server that enforces Basic Auth must answer an unauthenticated request with 401 and a WWW-Authenticate: Basic challenge, and a browser replaying cached credentials cannot produce that. This is the loopback check [50006] named as the intended robust evaluation when the function was introduced for #52066.
wp_is_site_protected_by_basic_auth() keeps its existing credential check. When credentials are present, it now confirms with the server through a new private helper, wp_is_basic_auth_enforced_by_server(): an unauthenticated wp_remote_head() to home_url( '/' ) looking for a 401 plus a Basic challenge.
- No cost for unaffected sites. The check is gated behind the existing credential check, so a request carrying no Basic Auth credentials never triggers it. Behaviour for those sites is unchanged.
- Cached. The result is stored in the
wp_basic_auth_enforcedtransient for 15 minutes. The lifetime is deliberately short: the server's configuration can change at any time, and a long-lived cache reproduces this ticket's own symptom until it expires. - Fails closed. If the loopback request errors, the function returns
true, exactly as before this check existed, and the failure is not cached so the next request can retry. Sites behind Basic Auth with loopback requests blocked see no change, so the conflict #52066 was opened to prevent stays prevented. - The filter is untouched.
wp_is_site_protected_by_basic_authstill short-circuits everything, so the documented__return_falseworkaround keeps working.
Tests
wp_is_site_protected_by_basic_auth() had no PHPUnit coverage at all. tests/phpunit/tests/load/wpIsSiteProtectedByBasicAuth.php adds it, mocking the loopback request through pre_http_request:
- no credentials present, and no loopback request made in that case
- each credential key present individually, and empty values
- stale credentials with a
200response →false(this ticket) 401with a non-Basic challenge →false401with a Basic challenge →true- loopback failure →
true, and not cached - the transient limits the check to a single request
- the
wp_is_site_protected_by_basic_authfilter overriding in both directions - context auto-detection on the login screen
Testing instructions
npm run test:php -- --filter Tests_Load_wpIsSiteProtectedByBasicAuth
Manual, reproducing the original report:
- Enable HTTP Basic Auth on the server. Load
Users → Profile → Application Passwords. The warning appears — correct. - Disable Basic Auth. Reload the profile screen in the same browser session. On trunk the warning persists and the form stays hidden; with this change the form is available again.
- Re-enable Basic Auth, delete the
wp_basic_auth_enforcedtransient, and reload. The warning returns. - A second browser or private window behaves the same in both directions.
Verified on trunk, PHP 8.5 (fpm-fcgi), nginx 1.31.5:
| Case | Before | After |
|---|---|---|
| No credentials in the request | false | false
|
Authorization: Basic for a credential the server has never seen, no Basic Auth configured | true | false
|
nginx auth_basic enabled, valid credentials | true | true
|
The middle row is this ticket. The last row is the #52066 behaviour, unchanged.
Open questions
- The transient name and the 15 minute lifetime are my choice — happy to change either. There is no hook for "the server's Basic Auth configuration changed", so a bounded lifetime seemed the honest trade-off.
- Failing closed on a loopback error is the conservative option, but it does mean a site with loopback requests blocked keeps seeing the current behaviour. If preferred, that case could be surfaced in Site Health instead.
Props ethicaladitya, khokansardar, smeunus.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Confirmed on trunk @ 987d42d6ad. The report is accurate, and the cause is a known, documented limitation rather than a regression.
Environment
Mechanism
wp_is_site_protected_by_basic_auth()insrc/wp-includes/load.phpdecides entirely on this line:$is_protected = ! empty( $_SERVER['PHP_AUTH_USER'] ) || ! empty( $_SERVER['PHP_AUTH_PW'] );Under the CGI/FastCGI SAPI, PHP populates
PHP_AUTH_USERandPHP_AUTH_PWfrom the client'sAuthorization: Basicrequest header on its own. No server-side Basic Auth needs to be configured for those keys to appear.Browsers keep sending cached Basic credentials to an origin for the rest of the session, so after Basic Auth is switched off the browser keeps replaying the header, PHP keeps synthesising the two
$_SERVERkeys, andwp_is_site_protected_by_basic_auth( 'front' )keeps returningtrue. That is what hides the form and prints the notice atsrc/wp-admin/user-edit.php.A fresh browser or private window sends no header, so it works — exactly as reported.
Measured with Basic Auth completely disabled on the server, replaying a credential the server has never seen:
On the reporter's `PHP_AU
The ticket notes that neitheer disabling Basic Auth. They are present precisely on the requests that carry the stale header; a check made from CLI or froched credential will not see them. So this observation does not contradict the diagnosis, it is a symptom of it.
Why there is no in-reques
I configured genuine nginx
plusWWW-Authenticatewhen no credentials are sent) and compared the$_SERVER` fingerprint against the stale-credential case above:They are indistinguishable.
AUTH_TYPEandREMOTE_USERlook like they could discriminate, but stochips neither, so keying on them would under-detect sites that really are behind Basic Auth and reintroduce the conflict #52066 was opened to prevent.Existing guidance
This is the limitation the function documents: "this merely checks for the present of Basic Auth credentials ... In a future release, this evaluation may be made more robust." [50006], which introduced the function for #52066, names the intended fix: make a loopback request and check for a
WWW-Authenticateheader.Until then the supported workaround is the filter added by that same changeset:
add_filter( 'wp_is_site_protected_by_basic_auth', '__return_false' );Suggested next step
Needs a committer decision on whether to implement the loopback detection from [50006]. That is a behaviour change on every site plus a network request on the profile screen, so it needs caching and sign-off rather than a drive-by patch. Worth noting that
wp_is_site_protected_by_basic_auth()currently has no PHPUnit coverage at all, which would be useful groundwork either way.