Login and Registration: Add support for /.well-known/change-password#12621
Login and Registration: Add support for /.well-known/change-password#12621HasnainAshfaq wants to merge 4 commits into
Conversation
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
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
There was a problem hiding this comment.
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-passwordhandling towp_redirect_admin_locations()with a newwp_change_password_urlfilter. - 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.
| $password_change_urls = array( | ||
| home_url( '.well-known/change-password', 'relative' ), | ||
| site_url( '.well-known/change-password', 'relative' ), | ||
| ); |
| 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; | ||
| } |
| 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).' ); | ||
| } |
| 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
There was a problem hiding this comment.
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 = falseinfinally, 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;
| /** | ||
| * 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(); | ||
| } |
| $redirect = $this->get_redirect_for( '/.well-known/change-password' ); | ||
| $this->assertStringContainsString( 'profile.php', $redirect, 'Should redirect to the profile page.' ); |
| 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; |
| $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
There was a problem hiding this comment.
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 intear_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_404tofalsein thefinallyblock, which may not restore the prior value if it was alreadytrue(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,
$redirectwill benullandassertStringContainsString()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,
$redirectwill benullandassertStringContainsString()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-passwordand/.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
There was a problem hiding this comment.
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 tountrailingslashit( $_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.' );
}
| $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' ); |
Fixes https://core.trac.wordpress.org/ticket/51173
What
Adds a redirect from
/.well-known/change-passwordtowp-admin/profile.phpinside the existingwp_redirect_admin_locations()function incanonical.php. This follows the W3C Well-Known Change Password URL spec.Why
Browsers (Chrome, Safari, Firefox) and password managers use
/.well-known/change-passwordto automatically navigate users to the password-change page. Without this redirect, WordPress sites return a 404 for this URL, breaking password manager integrations.How
/.well-known/change-passworddetection towp_redirect_admin_locations(), mirroring the existing pattern for/loginand/adminaliases.admin_url( 'profile.php' )— the profile page handles unauthenticated users by redirecting them to the login page withredirect_toset back to the profile page.wp_change_password_urlfilter allows sites with custom user flows (membership plugins, WooCommerce accounts) to override the redirect target.Tests
Adds
tests/phpunit/tests/canonical/changePassword.phpcovering:/.well-known/change-passworduntrailingslashitbehavior elsewhere)wp_change_password_urlfilter is respected