Skip to content

Login and Registration: Add support for /.well-known/change-password - #12621

Closed
HasnainAshfaq wants to merge 13 commits into
WordPress:trunkfrom
HasnainAshfaq:feature/51173-well-known-change-password
Closed

HasnainAshfaq wants to merge 13 commits into
WordPress:trunkfrom
HasnainAshfaq:feature/51173-well-known-change-password

Conversation

@HasnainAshfaq

Copy link
Copy Markdown

Fixes https://core.trac.wordpress.org/ticket/51173

What

Adds a redirect from /.well-known/change-password to wp-admin/profile.php inside the existing wp_redirect_admin_locations() function in canonical.php. This follows the W3C Well-Known Change Password URL spec.

Why

Browsers (Chrome, Safari, Firefox) and password managers use /.well-known/change-password to automatically navigate users to the password-change page. Without this redirect, WordPress sites return a 404 for this URL, breaking password manager integrations.

How

  • Adds /.well-known/change-password detection to wp_redirect_admin_locations(), mirroring the existing pattern for /login and /admin aliases.
  • Redirects to admin_url( 'profile.php' ) — the profile page handles unauthenticated users by redirecting them to the login page with redirect_to set back to the profile page.
  • A wp_change_password_url filter allows sites with custom user flows (membership plugins, WooCommerce accounts) to override the redirect target.
  • Only fires when pretty permalinks are enabled (same guard as the rest of the function).

Tests

Adds tests/phpunit/tests/canonical/changePassword.php covering:

  • Redirect fires for /.well-known/change-password
  • Trailing-slash variant does not redirect (consistent with untrailingslashit behavior elsewhere)
  • No redirect when pretty permalinks are disabled
  • wp_change_password_url filter is respected

Adds a redirect from `/.well-known/change-password` to the user profile
page (`wp-admin/profile.php`) inside `wp_redirect_admin_locations()`.

Browsers and password managers use this well-known URL to help users
change their passwords. A `wp_change_password_url` filter is provided
for sites with custom user flows (e.g. membership plugins) to redirect
to a different URL.

Fixes #51173
Copilot AI review requested due to automatic review settings July 21, 2026 10:26
@github-actions

github-actions Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props hasnainashfaq, peterwilsoncc, masteradhoc.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for the W3C /.well-known/change-password endpoint by redirecting requests to WordPress’s password-change flow, plus PHPUnit coverage for the new redirect behavior.

Changes:

  • Add /.well-known/change-password handling to wp_redirect_admin_locations() with a new wp_change_password_url filter.
  • Redirect well-known change-password requests to admin_url( 'profile.php' ).
  • Add a new canonical test file covering redirect behavior, permalink requirements, and the filter.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/wp-includes/canonical.php Adds the well-known change-password redirect and introduces a filter for customizing the redirect target.
tests/phpunit/tests/canonical/changePassword.php Adds PHPUnit tests intended to validate the redirect behavior and filter integration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wp-includes/canonical.php Outdated
Comment on lines +1071 to +1074
$password_change_urls = array(
home_url( '.well-known/change-password', 'relative' ),
site_url( '.well-known/change-password', 'relative' ),
);
Comment thread tests/phpunit/tests/canonical/changePassword.php
Comment on lines +73 to +76
public function test_well_known_change_password_with_trailing_slash_does_not_redirect() {
$redirect = $this->get_redirect_for( '/.well-known/change-password/' );
$this->assertNull( $redirect, 'Trailing slash variant should not redirect (untrailingslashit handles exact match only).' );
}
Comment on lines +110 to +113
add_filter( 'wp_change_password_url', static fn() => $custom_url );
$redirect = $this->get_redirect_for( '/.well-known/change-password' );
remove_all_filters( 'wp_change_password_url' );

- Add root-relative `/.well-known/change-password` to the match array
  so subdirectory installs (e.g. example.com/blog/) are also covered
- Fix test helper to use exception interception instead of empty-string
  filter return, preventing premature exit() from killing PHPUnit
- Correct trailing-slash test: untrailingslashit() normalises the URI
  so it does match and redirect (test now asserts the redirect fires)
- Use remove_filter() instead of remove_all_filters() in filter test

Fixes #51173
Copilot AI review requested due to automatic review settings July 21, 2026 10:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

tests/phpunit/tests/canonical/changePassword.php:43

  • get_redirect_for() forces $wp_query->is_404 = false in finally, which may clobber the previous value if the caller/test had set it differently. Store the original value and restore it to keep test state isolated.
		global $wp_query;
		$wp_query->is_404 = true;

Comment on lines +13 to +29
/**
* Original REQUEST_URI value.
*
* @var string
*/
private $original_request_uri;

public function set_up() {
parent::set_up();
$this->original_request_uri = $_SERVER['REQUEST_URI'] ?? '';
$this->set_permalink_structure( '/%postname%/' );
}

public function tear_down() {
$_SERVER['REQUEST_URI'] = $this->original_request_uri;
parent::tear_down();
}
Comment on lines +68 to +69
$redirect = $this->get_redirect_for( '/.well-known/change-password' );
$this->assertStringContainsString( 'profile.php', $redirect, 'Should redirect to the profile page.' );
Comment thread src/wp-includes/canonical.php Outdated
Comment on lines +1077 to +1087
if ( in_array( untrailingslashit( $_SERVER['REQUEST_URI'] ), $password_change_urls, true ) ) {
/**
* Filters the URL to redirect to when a browser or password manager
* requests the well-known change-password URL (/.well-known/change-password).
*
* @since 6.9.0
*
* @param string $url The URL to redirect to. Default is the user profile page.
*/
wp_redirect( apply_filters( 'wp_change_password_url', admin_url( 'profile.php' ) ) );
exit;
Comment on lines +76 to +77
$redirect = $this->get_redirect_for( '/.well-known/change-password/' );
$this->assertStringContainsString( 'profile.php', $redirect, 'Trailing slash variant should also redirect to the profile page.' );
Aligns $filter assignment with surrounding variables per WPCS rules.

Fixes #51173
Copilot AI review requested due to automatic review settings July 21, 2026 11:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (5)

tests/phpunit/tests/canonical/changePassword.php:17

  • The test stores $_SERVER['REQUEST_URI'] as an empty string when the key is unset, and then always writes it back in tear_down(). This can change global state for other tests (key becomes set when it previously wasn’t). Preserve the original “unset vs set” state and restore accordingly.
	/**
	 * Original REQUEST_URI value.
	 *
	 * @var string
	 */

tests/phpunit/tests/canonical/changePassword.php:45

  • get_redirect_for() forces $wp_query->is_404 to false in the finally block, which may not restore the prior value if it was already true (or otherwise modified) before the helper ran. Back up the original value and restore it.
		global $wp_query;
		$wp_query->is_404 = true;

		$captured = null;

tests/phpunit/tests/canonical/changePassword.php:70

  • If no redirect fires, $redirect will be null and assertStringContainsString() can error (type mismatch) instead of producing a clear test failure. Add an explicit non-null assertion first for better failure output.
	public function test_well_known_change_password_redirects_to_profile() {
		$redirect = $this->get_redirect_for( '/.well-known/change-password' );
		$this->assertStringContainsString( 'profile.php', $redirect, 'Should redirect to the profile page.' );
	}

tests/phpunit/tests/canonical/changePassword.php:78

  • If no redirect fires, $redirect will be null and assertStringContainsString() can error (type mismatch) instead of producing a clear test failure. Add an explicit non-null assertion first for better failure output.
	public function test_well_known_change_password_with_trailing_slash_redirects_to_profile() {
		$redirect = $this->get_redirect_for( '/.well-known/change-password/' );
		$this->assertStringContainsString( 'profile.php', $redirect, 'Trailing slash variant should also redirect to the profile page.' );
	}

src/wp-includes/canonical.php:1081

  • The PR description says the trailing-slash variant should not redirect, but the implementation uses untrailingslashit( $_SERVER['REQUEST_URI'] ), which will match both /.well-known/change-password and /.well-known/change-password/. Please align either the description or the redirect matching logic/tests so they reflect the intended behavior.
	if ( in_array( untrailingslashit( $_SERVER['REQUEST_URI'] ), $password_change_urls, true ) ) {
		/**
		 * Filters the URL to redirect to when a browser or password manager
		 * requests the well-known change-password URL (/.well-known/change-password).
		 *

- Restore REQUEST_URI to truly unset state if it was not set before
  the test ran, rather than forcing it to an empty string
- Save and restore the original $wp_query->is_404 value in the helper
  instead of hard-coding false, preventing state leakage between tests
- Add assertNotNull() before assertStringContainsString() calls so a
  missing redirect produces a clear failure rather than a type error

Fixes #51173
Copilot AI review requested due to automatic review settings July 21, 2026 14:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

tests/phpunit/tests/canonical/changePassword.php:93

  • PR description says the trailing-slash variant (/.well-known/change-password/) "does not redirect", but both the implementation and this test expect it to redirect due to untrailingslashit( $_SERVER['REQUEST_URI'] ). Please align the intended behavior across the PR description + implementation + tests (either update the description, or change the matching logic/tests if the trailing-slash variant should truly not redirect).
	public function test_well_known_change_password_with_trailing_slash_redirects_to_profile() {
		$redirect = $this->get_redirect_for( '/.well-known/change-password/' );
		$this->assertNotNull( $redirect, 'A redirect should fire for the trailing-slash variant.' );
		$this->assertStringContainsString( 'profile.php', $redirect, 'Trailing slash variant should also redirect to the profile page.' );
	}

Comment thread tests/phpunit/tests/canonical/changePassword.php
@masteradhoc

Copy link
Copy Markdown

@HasnainAshfaq i've come across the PR. could you please check the open copilot feedback and fix/resolve all open comments - if valid? i'd like to check this and see if it could be helpful to push this further.

Comment thread src/wp-includes/canonical.php Outdated
* Filters the URL to redirect to when a browser or password manager
* requests the well-known change-password URL (/.well-known/change-password).
*
* @since 6.9.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 6.9.0
* @since 7.2.0

…SInCE tag.

- Match only the spec-defined root path '/.well-known/change-password'
  directly, removing the home_url() and site_url() relative variants.
  Per the W3C spec the well-known URL is always at the origin root, so
  the home/site-relative paths were incorrect for subdirectory installs
  and unnecessary for root installs.
- Update @SInCE tag to 7.2.0 per committer suggestion.
@HasnainAshfaq

Copy link
Copy Markdown
Author

Thanks for the review @masteradhoc! I've gone through all the open Copilot comments:

  • @since 7.2.0 — applied per your suggestion.
  • Subdirectory URL matching — removed the home_url()/site_url() relative variants; per the W3C spec the well-known URL is always at the origin root (/.well-known/change-password), so only that literal path is matched now.
  • Exception-based redirect interception — already in place from an earlier revision; the wp_redirect filter throws a RuntimeException caught in try/catch/finally, so exit is never reached in tests.
  • assertNotNull() before string assertions — already added.
  • tear_down() restoring only when set — already handled via $request_uri_was_set.
  • Targeted filter removal — already using specific callback references, not remove_all_filters().
  • Arrow function for filter callback — fn() => $custom_url does not need explicit parameter acceptance.
  • Trailing slash — untrailingslashit() strips it before comparison, so /.well-known/change-password/ correctly redirects (consistent with the /login and /admin behavior); tests updated to reflect this.

@peterwilsoncc peterwilsoncc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a suggestion inline to use wp_safe_redirect().

Comment thread src/wp-includes/canonical.php
Comment thread tests/phpunit/tests/canonical/changePassword.php
@peterwilsoncc

Copy link
Copy Markdown
Contributor

I've taken the liberty of pushing the following:

  • Updating the redirect to use wp_safe_redirect() with the docblock updated accordingly
  • Conditionally exiting only if the redirect succeeds
  • Removed a bunch of unnecessary tear_down code from the tests: the base test suite resets hooks, URL structure, the $_SERVER and wp_query
  • Improved redirect assertions to check the full URL rather than just for profile.php
  • Merged in trunk

@peterwilsoncc peterwilsoncc added the props-bot Adding this label triggers the Props Bot workflow for a PR. label Oct 14, 2026
@github-actions github-actions Bot removed the props-bot Adding this label triggers the Props Bot workflow for a PR. label Oct 14, 2026
wporg-sync pushed a commit that referenced this pull request Oct 14, 2026
Introduce support for the [https://w3c.github.io/webappsec-change-password-url/ Well-Known URL for Changing Passwords], a standardized URL for tools such as password managers to make change of password forms discoverable.

Per the specification this is only available to WordPress sites installed in the root directory of a domain. The URL `example.com/.well-known/change-password` will redirect a user to their profile page.

Introduces the filter `wp_change_password_url` to allow sites using a non-standard location for managing user passwords. If this is hosted on an another host, the host will need to be added to the allow list via the `allowed_redirect_hosts` filter in order to pass the `wp_safe_redirect()` validation process.

Developed in #12621

Props audrasjb, ayeshrajans, hasnainashfaq, jns141191, johnbillion, masteradhoc, pfefferle, romainmrhenry, sajib1223, sergeybiryukov, swissspidy.
Fixes #51173.


git-svn-id: https://develop.svn.wordpress.org/trunk@64348 602fd350-edb4-49c9-b593-d223f7449a82
@github-actions

Copy link
Copy Markdown

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 64348
GitHub commit: 0eb0884

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions Bot closed this Oct 14, 2026
wporg-sync pushed a commit to WordPress/WordPress that referenced this pull request Oct 14, 2026
Introduce support for the [https://w3c.github.io/webappsec-change-password-url/ Well-Known URL for Changing Passwords], a standardized URL for tools such as password managers to make change of password forms discoverable.

Per the specification this is only available to WordPress sites installed in the root directory of a domain. The URL `example.com/.well-known/change-password` will redirect a user to their profile page.

Introduces the filter `wp_change_password_url` to allow sites using a non-standard location for managing user passwords. If this is hosted on an another host, the host will need to be added to the allow list via the `allowed_redirect_hosts` filter in order to pass the `wp_safe_redirect()` validation process.

Developed in WordPress/wordpress-develop#12621

Props audrasjb, ayeshrajans, hasnainashfaq, jns141191, johnbillion, masteradhoc, pfefferle, romainmrhenry, sajib1223, sergeybiryukov, swissspidy.
Fixes #51173.

Built from https://develop.svn.wordpress.org/trunk@64348


git-svn-id: http://core.svn.wordpress.org/trunk@63498 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants