Skip to content

feat(oauth): serve OAuth2 redirects from a dedicated route - #206

Merged
RishadAlam merged 1 commit into
mainfrom
oauth2-callback
Aug 2, 2026
Merged

feat(oauth): serve OAuth2 redirects from a dedicated route#206
RishadAlam merged 1 commit into
mainfrom
oauth2-callback

Conversation

@RishadAlam

Copy link
Copy Markdown
Member

Description

Adds a dedicated OAuth2 redirect endpoint at /bit-integrations/oauth-callback/ and makes it the URI the plugin hands to providers. The legacy REST endpoint wp-json/bit-integrations/v1/redirect stays registered and shares the same implementation, so apps still configured with it keep working.

Motivation & Context

The admin app is hash-routed, so a provider can never redirect straight to it — every OAuth flow has to bounce through a proxy endpoint that forwards the callback params to the URL carried in state. That proxy lived under wp-json, which some providers reject as a redirect_uri and which reads as an API path rather than a plugin route. Bit-Pi already serves this from a rewrite-rule route; this brings Bit Integrations in line.

While wiring it up, the redirect URI turned out to be hardcoded as ${btcbi.api}/redirect in four separate frontend files, so it now comes from one place in PHP and is filterable.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation update
  • ⚡ Improvement
  • 🔄 Code refactor

⚠️ Breaking Change

The redirect URI sent to OAuth providers changes:

before: https://site.com/wp-json/bit-integrations/v1/redirect
after:  https://site.com/bit-integrations/oauth-callback/

That URI is registered inside the user's own OAuth app (Google Cloud Console, Zoho, Facebook, …), which we cannot update for them. Existing users must add the new URI to their app before their next re-authorization, or it fails with redirect_uri_mismatch. Existing tokens and refreshes are unaffected — only the authorization-code step sends redirect_uri.

Both endpoints work, so a site can pin back to the old one:

add_filter('bit_integrations_oauth_redirect_uri', function () {
    return BitApps\Integrations\Config::get('API_URL') . '/redirect';
});

Key Changes

Backend

  • Added Core\Http\OauthCallbackController — serves the new route, forwards provider params onto the state URL, and exposes redirectToState() shared by both endpoints.
  • Added Core\Util\RewriteRuleProvider — maps a plugin route to WordPress' pagename query var, with a self-heal that re-flushes when another plugin drops the rule, rate limited to one flush per hour.
  • Added Config::get('OAUTH_CALLBACK_URI') and Config::get('REDIRECT_URI'), the latter filterable via bit_integrations_oauth_redirect_uri and localized to the frontend as redirectURI.
  • Added Request::isRest() — detects a REST request before REST_REQUEST is defined, since that only happens once parse_request runs.
  • Updated the handler to run on parse_request rather than template_include, so it resolves before the main query and before redirect_canonical can rewrite the URL through 404 handling.
  • Updated Deactivation to flush rewrite rules, so the route does not linger in the rewrite_rules option after deactivating.

Frontend

  • Updated four hardcoded ${btcbi.api}/redirect sites to read the single getRedirectUri() helper, which now prefers the localized redirectURI.

Security

  • Added rejection of any state whose host and port differ from the site — javascript:, protocol-relative and userinfo-prefixed URLs are all refused before wp_safe_redirect().
  • Added X-Robots-Tag: noindex, nofollow and nocache_headers() on the callback, so a shared proxy cannot cache one user's redirect and replay it to the next.

Bug fixes in the legacy endpoint

  • Fixed the redirect URL being joined with a hardcoded &, which produced a malformed URL for any state without a ?.
  • Fixed a rejected state returning HTTP 500 with an empty body instead of a 404 with a message.
  • Removed a commented-out list() method, an orphaned FilesystemIterator import and an unused getHostWithPort() helper.

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Tests added/updated
  • Documentation updated if needed
  • README updated if needed

Testing

Verified against a local install with pretty permalinks:

Case Result
New route with code + scope 302 to the admin URL, params appended after the hash
Plain-permalink form ?pagename=… 302, identical Location
Legacy REST route 302, byte-identical to before this PR
Params containing encoded characters Preserved, and identical across both endpoints
Off-site, protocol-relative and userinfo state 404, no redirect
Missing state 404
Rewrite rule deleted by hand, then a page load Rule restored, flush lock set
/wp-json/, ?rest_route=/, wp-login.php 200, unaffected

Changelog

  • New Feature

  • OAuth: Authorizations now redirect through /bit-integrations/oauth-callback/ instead of a wp-json URL. Add the new redirect URL to your app in the provider's console before re-authorizing; the old URL keeps working for apps already set up with it.

  • Improvement

  • OAuth: The redirect URL is now filterable through bit_integrations_oauth_redirect_uri.

  • OAuth: Callback responses are marked no-index and no-store.

  • Bug Fixes

  • OAuth: Fixed a malformed redirect when the return URL carried no query string.

Copilot AI review requested due to automatic review settings August 1, 2026 10:21

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@RishadAlam
RishadAlam merged commit 279ee38 into main Aug 2, 2026
1 check passed
@RishadAlam
RishadAlam deleted the oauth2-callback branch August 2, 2026 06:51
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.

3 participants