Skip to content

Revoke consent by invalidating tokens#23425

Merged
diedexx merged 12 commits into
trunkfrom
fix/revoking-consent-creates-tokens
Jul 3, 2026
Merged

Revoke consent by invalidating tokens#23425
diedexx merged 12 commits into
trunkfrom
fix/revoking-consent-creates-tokens

Conversation

@leonidasmi

@leonidasmi leonidasmi commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Context

  • Ever since 23302 manage user consent through yoast ai #23306, revoking consent happened after invalidating tokens but revoking consent actually created new tokens, so that needed to change:
    • We now do a DELETE to the revoke token endpoint, which then is followed by a token invalidation if there are tokens locally
    • For OAuth flows, the token invalidation will probably not happen unless there's residual tokens
  • We also fix a regression from this PR, that broke granting consent in JWT-based sites
    • The bug came from the fact that an empty array as body breaks POST requests, so we made it so that when an empty array is passed as body, we omit it from the request.
  • We also cascaded our changes in the ai- structure, but we dont have to test the new Free + 27.1 Premium as per this, so the code changes in those files dont come with test instructions. We do ship those changes for completeness sake.

Summary

This PR can be summarized in the following changelog entry:

  • Properly revokes consent in both JWT- and OAuth-based flows.
  • Fixes an unreleased bug where granting consent in JWT-based flows was broken.

Relevant technical choices:

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

Do the below steps with

  • logging enabled in the Test help and
    • Premium updated
    • no Premium active

Testing in JWT-based flow

  • Make sure you dont have the define( 'YOAST_SEO_MYYOAST_CONNECTION', true ); active
  • In Users->My Profile, click Grant consent and confirm that everything went great.
    • Go to use one of our AI features and confirm they work
  • In Users->My Profile, open the Network tab, click Revoke consent and confirm that everything went great.
    • Confirm in the Network tab that the request returned with 200 and a success message
    • Confirm now the button says Grant consent
    • Verify the AI features get disabled.
    • Also confirm that _yoast_wpseo_ai_generator_access_jwt and _yoast_wpseo_ai_generator_refresh_jwt doesn't exists in your usermeta
  • To test revoke consent failure, first grant consent again
    • Confirm that _yoast_wpseo_ai_generator_access_jwt and _yoast_wpseo_ai_generator_refresh_jwt exists in your usermeta
  • then add the following snippet
add_filter( 'pre_http_request', function ( $preempt, $args, $url ) {
    $path = (string) wp_parse_url( $url, PHP_URL_PATH );
    if ( ! preg_match( '#/token/invalidate$#', $path ) ) {
        return $preempt;
    }
    if ( ( $args['method'] ?? '' ) !== 'POST' ) {
        return $preempt;
    }
    return [
        'response' => [ 'code' => 500, 'message' => 'Internal Server Error' ],
        'body'     => '{"message":"Simulated AI service failure","status":500}',
        'headers'  => [], 'cookies' => [], 'filename' => null,
    ];
}, 10, 3 );

add_filter( 'pre_http_request', function ( $preempt, $args, $url ) {
    // Match on the URL path only: the OAuth flow appends `?user_id=…` to DELETE
    // requests, which breaks a `$`-anchored match against the full URL.
    $path = (string) wp_parse_url( $url, PHP_URL_PATH );
    if ( ! preg_match( '#/user/consent$#', $path ) ) {
        return $preempt;
    }
    if ( ( $args['method'] ?? '' ) !== 'DELETE' ) {
        return $preempt;
    }
    return [
        'response' => [ 'code' => 500, 'message' => 'Internal Server Error' ],
        'body'     => '{"message":"Simulated AI service failure","status":500}',
        'headers'  => [], 'cookies' => [], 'filename' => null,
    ];
}, 10, 3 );
  • The above snippet simulates failure in both the request of revoking consent and the subsequent request of invalidating tokens.
  • Visit your WordPress user Profile and click Revoke consent.
    • Verify in the network tab that the request failed with 500 status and a Failed to revoke consent message
    • Verify the modal does NOT show an error → revoke is security-first, the route returns success regardless.
    • Verify in the logger that you see
      • WARNING - Primary AI auth strategy failed (UNKNOWN, HTTP 500: Simulated AI service failure); the failure is not recoverable by the fallback, propagating to the caller. which is the failure of the revoke consent
      • ERROR - Simulated AI service failure which is the failure of the token invalidation
    • Verify the AI features get disabled.
    • In wp_usermeta, verify _yoast_wpseo_ai_consent for your user is deleted despite the simulated failure.
    • In wp_usermeta, verify _yoast_wpseo_ai_generator_access_jwt for your user is deleted despite the simulated failure. Same for _yoast_wpseo_ai_generator_refresh_jwt
  • Remove the snippet when done with this section.
  • As a final test, have consent revoked and go to grant consent from the block editor, using one of our AI features
    • Confirm that once you gave consent, the AI feature (eg. the AI generate) was able to be triggered

Testing in OAuth-based flow

  • Do the tests in the same site as above and make sure you have not revoked consent before going to the below steps
    • We basically want us to have _yoast_wpseo_ai_generator_access_jwt tokens locally, to make sure they can cleaned up after.
  • Make sure you have the define( 'YOAST_SEO_MYYOAST_CONNECTION', true ); active
  • Repeat the above tests.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • AI features still work both with Premium on and off.

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes #

@leonidasmi leonidasmi added the changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog label Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 0

Warning

No base build found for commit aa39981 on 23408-fix-ai-consent-related-classes-in-the-old-ai-folder-structure.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 49.455%

Details

  • Patch coverage: 2 uncovered changes across 2 files (2 of 4 lines covered, 50.0%).

Uncovered Changes

File Changed Covered %
src/ai-consent/application/consent-handler.php 1 0 0.0%
src/ai-consent/user-interface/consent-route.php 1 0 0.0%
Total (4 files) 4 2 50.0%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 41525
Covered Lines: 20536
Line Coverage: 49.45%
Coverage Strength: 4.11 hits per line

💛 - Coveralls

Base automatically changed from 23408-fix-ai-consent-related-classes-in-the-old-ai-folder-structure to trunk July 1, 2026 10:24
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

A merge conflict has been detected for the proposed code changes in this PR. Please resolve the conflict by either rebasing the PR or merging in changes from the base branch.

leonidasmi and others added 5 commits July 1, 2026 14:06
Resolve conflicts in the new-structure Consent_Handler and its Revoke_Consent_Test by taking trunk's auth-strategy refactor; our route, token-manager, and legacy revoke changes merge cleanly on top.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Updates the Yoast AI consent revocation flow to avoid re-provisioning tokens when revoking consent, aligning JWT-based revocation with the /token/invalidate endpoint while keeping OAuth-based revocation on DELETE /user/consent. It also fixes a regression where sending an empty array body on POST could break requests by omitting empty bodies entirely.

Changes:

  • Reworks consent revocation so JWT-based sites revoke consent via token invalidation (and always clear local JWTs), while OAuth-based sites revoke via DELETE /user/consent.
  • Ensures empty POST bodies are omitted (Request body becomes null when empty, and API client skips encoding/sending it).
  • Adds/updates unit tests for revoke-consent behavior and for token invalidation clearing local tokens even when the remote call fails.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Unit/AI/Consent/User_Interface/Consent_Route/Consent_Test.php Updates route tests to reflect revocation being handled by the consent handler (no direct token invalidation call expected).
tests/Unit/AI/Consent/Application/Consent_Handler/Revoke_Consent_Test.php Adjusts expectations to the new revoke behavior (no longer asserting a returned Response from revoke call).
tests/Unit/AI/Authorization/Application/Token_Manager/Token_Invalidate_Test.php Aligns token invalidation request expectations (no body) and adds coverage for local token clearing on remote failure.
tests/Unit/AI/Authentication/Application/Token_Auth_Strategy/Token_Auth_Strategy_Test.php Adds a test ensuring revoke consent invalidates tokens and does not provision new ones.
tests/Unit/AI/Authentication/Application/OAuth_Auth_Strategy/Revoke_Consent_Test.php New test verifying OAuth revoke consent dispatches authenticated DELETE with user_id in query and no body.
tests/Unit/AI/Authentication/Application/AI_Request_Sender/AI_Request_Sender_Test.php Adds a test ensuring revoke consent delegates to primary strategy only; updates expectations for empty bodies becoming null.
src/ai/http-request/infrastructure/api-client.php Omits POST body entirely when empty to avoid AI service rejecting [].
src/ai/http-request/infrastructure/api-client-interface.php Updates PHPDoc to allow null/empty body semantics.
src/ai/http-request/domain/request.php Changes get_body() to return null for empty arrays to represent “no body”.
src/ai/consent/user-interface/consent-route.php Switches revoke path to Consent_Handler::revoke_consent() (no longer calling Token_Manager directly).
src/ai/authorization/application/token-manager.php Sends token invalidation with no body and always clears local tokens in a finally block.
src/ai/authentication/application/token-auth-strategy.php Implements revoke_consent() by invalidating tokens (no new token provisioning).
src/ai/authentication/application/oauth-auth-strategy.php Safely merges nullable request bodies for POST; adds revoke_consent() via authenticated DELETE.
src/ai/authentication/application/auth-strategy-interface.php Adds revoke_consent() to the auth strategy contract.
src/ai/authentication/application/ai-request-sender.php Changes revoke consent to delegate only to the primary strategy and return void.
src/ai-http-request/infrastructure/api-client.php Mirrors empty-body omission behavior in the ai- mirrored structure.
src/ai-http-request/infrastructure/api-client-interface.php Mirrors PHPDoc updates in the ai- mirrored structure.
src/ai-http-request/domain/request.php Mirrors get_body(): ?array empty-body behavior in the ai- mirrored structure.
src/ai-consent/user-interface/consent-route.php Mirrors route revoke delegation change in the ai- mirrored structure.
src/ai-consent/application/consent-handler.php Updates revoke to use token invalidation instead of DELETE /user/consent (legacy/JWT path).
src/ai-authorization/application/token-manager.php Mirrors token invalidation changes (no body + always clear local tokens) in the ai- mirrored structure.

Comment thread src/ai-consent/application/consent-handler.php
Comment thread src/ai/consent/user-interface/consent-route.php
Comment thread src/ai-consent/user-interface/consent-route.php
@leonidasmi leonidasmi marked this pull request as ready for review July 2, 2026 12:03
Comment thread src/ai/authentication/application/ai-request-sender.php Outdated
Comment thread src/ai/authentication/application/auth-strategy-interface.php Outdated

@diedexx diedexx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CR & ACC

@diedexx diedexx merged commit bba276c into trunk Jul 3, 2026
26 checks passed
@diedexx diedexx deleted the fix/revoking-consent-creates-tokens branch July 3, 2026 11:54
@diedexx diedexx modified the milestone: 28.0 Jul 3, 2026
@diedexx diedexx added this to the 28.1 milestone Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants