Skip to content

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

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

Login and Registration: Add support for /.well-known/change-password#12621
HasnainAshfaq wants to merge 4 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

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.

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 on lines +1071 to +1074
$password_change_urls = array(
home_url( '.well-known/change-password', 'relative' ),
site_url( '.well-known/change-password', 'relative' ),
);
Comment on lines +38 to +60
private function get_redirect_for( $request_uri ) {
$_SERVER['REQUEST_URI'] = $request_uri;

global $wp_query;
$wp_query->is_404 = true;

$captured = null;

$capture = static function ( $location ) use ( &$captured ) {
$captured = $location;
// Return empty string so wp_redirect() sends no Location header
// and doesn't exit; execution returns to our caller.
return '';
};

add_filter( 'wp_redirect', $capture );
wp_redirect_admin_locations();
remove_filter( 'wp_redirect', $capture );

$wp_query->is_404 = false;

return $captured;
}
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 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 on lines +110 to +114
$custom_url = 'https://example.com/my-account/change-password/';
$filter = static fn() => $custom_url;

add_filter( 'wp_change_password_url', $filter );
$redirect = $this->get_redirect_for( '/.well-known/change-password' );
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.

2 participants