Align endpoints with IndieAuth spec changes since 2020#311
Open
pfefferle wants to merge 5 commits into
Open
Conversation
Addresses #294 for the paths that were removed from the specification since 2020 but are still supported for older clients: - response_type=id and omitted response_type on the authorization endpoint (removed in IndieAuth 1.1, response_type=code is required) - Token verification via GET on the token endpoint (replaced by the introspection endpoint) - Token revocation via action=revoke on the token endpoint (replaced by the revocation endpoint) All three keep working unchanged, but now emit a _doing_it_wrong() notice (logged when WP_DEBUG, visible in Query Monitor, hookable via doing_it_wrong_run) and send Deprecation and Link headers so client developers can discover the replacements.
Implements the spec requirements added since 2020 that were still missing: - Authorization requests: if the scheme, host or port of redirect_uri differ from client_id, the redirect_uri must match one of the redirect URLs published by the client (JSON client metadata redirect_uris, rel=redirect_uri link tags, or Link headers). Enforced in the REST authorization endpoint and in the consent form handler, filterable via pre_indieauth_client_redirect_uris and indieauth_verify_redirect_uri. - Introspection endpoint: the spec requires some form of authorization. Any authentication that establishes a WordPress user is accepted (IndieAuth bearer token, application password, session). Filtering indieauth_introspection_auth_methods_supported to none restores the previous unauthenticated behavior. - Client_Discovery now extracts published redirect_uris and fixes two latent bugs from the namespacing refactor: Mf2\parse() resolved to IndieAuth\Mf2\parse() (fatal for HTML clients) and the DOMDocument fallback passed HTML to the constructor instead of loadHTML().
- Add IndieAuth\same_origin() and use it in verify_redirect_uri(), which also gains default-port normalization to match the FedCM origin check. - Add IndieAuth\add_deprecation_headers() and replace the three copy-pasted Deprecation/Link header blocks. - Collapse the legacy response_type branch in the authorization GET handler into the single code() dispatch path. - Cache discovered client redirect URIs in a short-lived transient; the authorization flow verifies twice per flow and re-fetched the client document each time. - Merge the two identical deprecation tests into one data-provider test.
Contributor
There was a problem hiding this comment.
Pull request overview
Updates the plugin’s IndieAuth endpoints to match post-2020 spec changes by (1) deprecating legacy behaviors while keeping backward compatibility, (2) tightening security requirements around redirect_uri verification and introspection authorization, and (3) fixing client discovery parsing issues uncovered via tests.
Changes:
- Add deprecation notices + response headers for legacy
response_type/ token verification GET /action=revokebehaviors. - Enforce spec-required
redirect_uriverification for cross-origin redirects during authorization + consent handling. - Require authorization for introspection by default; expand client discovery to extract published redirect URIs (JSON/HTML/Link headers) and fix parsing bugs.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| includes/rest/class-token-controller.php | Emits _doing_it_wrong() for deprecated token behaviors and adds deprecation headers on success responses. |
| includes/rest/class-introspection-controller.php | Changes introspection to require authorization by default via a permission callback, with a filter to re-enable unauthenticated use. |
| includes/rest/class-authorization-controller.php | Adds legacy response_type deprecation signaling, implements redirect_uri verification, and caches discovered redirect URIs. |
| includes/functions.php | Introduces same_origin() and add_deprecation_headers() helpers. |
| includes/class-client-discovery.php | Adds redirect URI discovery (JSON/mf2/Link headers) and fixes HTML parsing / namespacing issues. |
| tests/phpunit/tests/includes/rest/class-test-token-controller.php | Adds tests asserting deprecation signals for legacy token verification/revocation behaviors. |
| tests/phpunit/tests/includes/rest/class-test-introspection-controller.php | Updates introspection tests for new auth requirement and filter-based opt-out. |
| tests/phpunit/tests/includes/rest/class-test-authorization-controller.php | Adds tests for legacy response_type deprecation headers and redirect URI verification behavior. |
| tests/phpunit/tests/includes/class-test-client-discovery.php | New unit tests for redirect URI extraction from JSON/HTML/Link headers. |
| readme.md | Documents the new deprecations and spec-alignment changes under “Unreleased”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+110
to
114
| if ( 'localhost' === \wp_parse_url( $this->client_id, PHP_URL_HOST ) ) { | ||
| return; | ||
| } | ||
| $response = self::parse( $client_id ); | ||
| $response = self::parse( $this->client_id ); | ||
| if ( \is_wp_error( $response ) ) { |
Comment on lines
174
to
+178
| if ( ! $token ) { | ||
| return new OAuth_Response( 'invalid_token', \__( 'Invalid access token', 'indieauth' ), 401 ); | ||
| } | ||
| $token['active'] = 'true'; | ||
| return \rest_ensure_response( $token ); | ||
| return add_deprecation_headers( \rest_ensure_response( $token ) ); |
Comment on lines
207
to
212
| if ( isset( $params['token'] ) ) { | ||
| $this->delete_token( $params['token'] ); | ||
| return \__( 'The Token Provided is No Longer Valid', 'indieauth' ); | ||
| return add_deprecation_headers( \rest_ensure_response( \__( 'The Token Provided is No Longer Valid', 'indieauth' ) ) ); | ||
| } else { | ||
| return new OAuth_Response( 'invalid_request', \__( 'Revoke is Missing Required Parameter token', 'indieauth' ), 400 ); | ||
| } |
Comment on lines
+401
to
+422
| function same_origin( $url1, $url2 ) { | ||
| $parts1 = \wp_parse_url( $url1 ); | ||
| $parts2 = \wp_parse_url( $url2 ); | ||
|
|
||
| if ( ! isset( $parts1['scheme'], $parts1['host'], $parts2['scheme'], $parts2['host'] ) ) { | ||
| return false; | ||
| } | ||
|
|
||
| if ( $parts1['scheme'] !== $parts2['scheme'] || $parts1['host'] !== $parts2['host'] ) { | ||
| return false; | ||
| } | ||
|
|
||
| $defaults = array( | ||
| 'http' => 80, | ||
| 'https' => 443, | ||
| ); | ||
| $default_port = isset( $defaults[ $parts1['scheme'] ] ) ? $defaults[ $parts1['scheme'] ] : null; | ||
| $port1 = isset( $parts1['port'] ) ? (int) $parts1['port'] : $default_port; | ||
| $port2 = isset( $parts2['port'] ) ? (int) $parts2['port'] : $default_port; | ||
|
|
||
| return $port1 === $port2; | ||
| } |
Comment on lines
+435
to
+440
| function add_deprecation_headers( $response ) { | ||
| $response->header( 'Deprecation', 'true' ); | ||
| $response->header( 'Link', '<https://github.com/indieweb/wordpress-indieauth/issues/294>; rel="deprecation"' ); | ||
|
|
||
| return $response; | ||
| } |
Comment on lines
+293
to
+297
| $this->assertEquals( 200, $response->get_status(), 'Response: ' . wp_json_encode( $response ) ); | ||
| $headers = $response->get_headers(); | ||
| $this->assertArrayHasKey( 'Deprecation', $headers ); | ||
| $this->assertStringContainsString( 'rel="deprecation"', $headers['Link'] ); | ||
| } |
Comment on lines
+336
to
+340
| $this->assertEquals( 200, $response->get_status(), 'Response: ' . wp_json_encode( $response ) ); | ||
| $headers = $response->get_headers(); | ||
| $this->assertArrayHasKey( 'Deprecation', $headers ); | ||
| $this->assertStringContainsString( 'rel="deprecation"', $headers['Link'] ); | ||
| $this->assertFalse( self::get_access_token( $token ) ); |
Comment on lines
+96
to
+100
| $this->assertEquals( 302, $response->get_status(), 'Response: ' . wp_json_encode( $response ) ); | ||
| $headers = $response->get_headers(); | ||
| $this->assertArrayHasKey( 'Deprecation', $headers ); | ||
| $this->assertStringContainsString( 'rel="deprecation"', $headers['Link'] ); | ||
| } |
Comment on lines
+131
to
+145
| add_filter( | ||
| 'indieauth_introspection_auth_methods_supported', | ||
| function () { | ||
| return array( 'none' ); | ||
| } | ||
| ); | ||
| $token = self::set_access_token(); | ||
| $response = $this->create_form( 'POST', | ||
| array( | ||
| 'token' => $token | ||
| ) | ||
| ); | ||
| remove_all_filters( 'indieauth_introspection_auth_methods_supported' ); | ||
| $this->assertEquals( 200, $response->get_status(), 'Response: ' . wp_json_encode( $response ) ); | ||
| } |
Comment on lines
+143
to
+161
| add_filter( | ||
| 'pre_indieauth_client_redirect_uris', | ||
| function () { | ||
| return array( 'https://other.example.net/redirect' ); | ||
| } | ||
| ); | ||
| $response = $this->create_get( | ||
| array( | ||
| 'response_type' => 'code', | ||
| 'client_id' => 'https://app.example.com', | ||
| 'redirect_uri' => 'https://other.example.net/redirect', | ||
| 'state' => '12345', | ||
| 'code_challenge' => 'OfYAxt8zU2dAPDWQxTAUIteRzMsoj9QBdMIVEDOErUo', | ||
| 'code_challenge_method' => 'S256', | ||
| ) | ||
| ); | ||
| remove_all_filters( 'pre_indieauth_client_redirect_uris' ); | ||
| $this->assertEquals( 302, $response->get_status(), 'Response: ' . wp_json_encode( $response ) ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #294.
Audit result
Reviewed the code against the current IndieAuth living spec. Already implemented:
issparameter, metadata endpoint, introspection + revocation + userinfo endpoints, refresh tokens, optionalme, JSON client metadata with h-app fallback. Non-PKCE clients are already rejected outright (stricter than spec). Revocation correctly advertisesnoneauth per spec.Deprecations (legacy behaviors removed from the spec — still work, now warn)
response_type=id/ omittedresponse_typeresponse_type=codeaction=revokeEach emits a
_doing_it_wrong()notice (logged withWP_DEBUG, hookable viadoing_it_wrong_run) and sendsDeprecation: true+Link: <…>; rel="deprecation"headers. Behavior is otherwise unchanged; custom actions viaindieauth_token_action_handlerare untouched.New spec requirements implemented
redirect_uriverification: when scheme/host/port differ fromclient_id, theredirect_urimust match a redirect URL published by the client (client metadataredirect_uris,rel="redirect_uri"link tags, orLinkheaders). Enforced at the REST authorization endpoint and the consent form handler. Filters:pre_indieauth_client_redirect_uris(short-circuit/cache),indieauth_verify_redirect_uri(override).Bearer; filterindieauth_introspection_auth_methods_supportedtononeto restore the old behavior.Bug fixes (found by the new Client_Discovery tests)
Mf2\parse()insidenamespace IndieAuthresolved toIndieAuth\Mf2\parse()— fatal for every HTML client since the namespacing refactor.DOMDocumentfallback passed HTML to the constructor (which takes an XML version string) instead ofloadHTML(), and crashed on missing titles/icons.Testing
TDD throughout — every test was watched failing first. 81 tests / 210 assertions pass on PHP 7.4 and 8.3 (wp-env); phpcs clean.