Skip to content

feat: AJAX uses token authentication#181

Open
dcalhoun wants to merge 31 commits intotrunkfrom
feat/authorize-ajax-with-application-passwords
Open

feat: AJAX uses token authentication#181
dcalhoun wants to merge 31 commits intotrunkfrom
feat/authorize-ajax-with-application-passwords

Conversation

@dcalhoun
Copy link
Copy Markdown
Member

@dcalhoun dcalhoun commented Sep 19, 2025

Related:

What?

Authenticate AJAX requests with application passwords sent via an authorization header. Vendor wp-util.js and configure the WordPress AJAX and media globals.

Why?

The GutenbergKit editor does not have authorization cookies, so we must rely upon a different authorization mechanism. Additionally, GutenbergKit excludes core WordPress assets from the editor assets endpoint, so wp-util.js (which provides wp.ajax and wp.template) must be vendored and loaded directly.

Ref CMM-713. Close CMM-768.

How?

  • Set the Authorization header via jQuery.ajaxPrefilter and by overloading the window.wp.ajax utilities. This general-purpose AJAX auth is always initialized.
  • Vendor and load wp-util.js after jQuery and lodash are on window, since its IIFE captures jQuery via closure at execution time.
  • Filter the lodash-js-after inline script from editor assets. WordPress's _.noConflict() call wipes window._ because GutenbergKit doesn't load Underscore.js.
  • Alias wp.media.ajax and wp.media.post to the authenticated wp.ajax methods, since WordPress core's media-models.js is not loaded.
  • Conditionally initialize the VideoPress AJAX bridge when videopress/video is not in allowed_block_types, so core/video blocks extended to rely upon VideoPress upload services continue to work.
  • Derive the Android WebViewAssetLoader domain from siteURL so that the editor document shares the site's origin, making REST API and admin-ajax.php requests same-origin and eliminating CORS restrictions without server-side headers.
  • Fix the iOS demo app to use the home URL from the REST API response instead of url, which returns http:// for WordPress.com sites.

Testing Instructions

1. Verify AJAX requests use token authentication

  1. make build
  2. Run the demo app in Xcode/Android Studio (use production build, not dev server).
  3. Open the editor for a site with an active Jetpack plugin connection (n.b., the demo app now supports WP.com sites).
  4. Open the Safari/Chrome developer tools on your development machine and inspect the network requests for the iOS (Developer Tools menu)/Android (chrome://inspect) device.
  5. Manually perform an AJAX request in the dev tools console via:
    wp.ajax.send('videopress-get-upload-jwt')
      .done( ( response ) => console.log( 'Success:', response ) )
      .fail( ( error ) => console.error( 'Error:', error ) );
  6. Verify the requests include the Authorization header with the token.
  7. Verify a 200 response with an upload token in the body.

2. Verify VideoPress bridge continues functioning

  1. make build
  2. Run the demo app in Xcode/Android Studio (use production build, not dev server).
  3. Open the editor for a site with an active Jetpack plugin connection (n.b., the demo app now supports WP.com sites).
  4. Open the Safari/Chrome developer tools on your development machine and inspect the network requests for the iOS (Developer Tools menu)/Android (chrome://inspect) device.
  5. Insert a Video block.
  6. Attach media to the block via upload.
  7. Verify the upload succeeds.
  8. Verify the request relies upon the REST API, not admin-ajax.
  9. Verify a 200 response with an upload token in the body.

Accessibility Testing Instructions

N/A, no navigation changes.

Screenshots or screencast

N/A, no visual changes.

@dcalhoun dcalhoun added the [Type] Enhancement A suggestion for improvement. label Sep 19, 2025
@dcalhoun dcalhoun force-pushed the feat/authorize-ajax-with-application-passwords branch from a051ea4 to 682e0df Compare September 19, 2025 20:00
@dcalhoun dcalhoun changed the title feat: Authorize AJAX with application passwords feat: Authorize AJAX with token authentication Sep 24, 2025
@dcalhoun dcalhoun changed the title feat: Authorize AJAX with token authentication feat: AJAX uses token authentication Sep 25, 2025
@dcalhoun dcalhoun force-pushed the feat/authorize-ajax-with-application-passwords branch from 686ebf7 to bbaf9c4 Compare January 13, 2026 19:35
@dcalhoun
Copy link
Copy Markdown
Member Author

@claude

@claude

This comment was marked as outdated.

@dcalhoun dcalhoun force-pushed the feat/authorize-ajax-with-application-passwords branch from 394bce2 to ea73b3c Compare February 12, 2026 17:50
@dcalhoun dcalhoun force-pushed the feat/authorize-ajax-with-application-passwords branch 2 times, most recently from e2ead31 to 80aca45 Compare March 25, 2026 19:58
dcalhoun and others added 11 commits March 26, 2026 13:06
Include authorization header in AJAX requets, as we do not have cookies
to send in the mobile app environment.
If we configure AJAX before loading the library, the configuration is
overridden.
This global is often used by WordPress Admin page scripts.
Useful when needing to allow CORS for specific domains.
Address PR feedback about potential race condition. The code now checks
if `window.wp.ajax.send` and `window.wp.ajax.post` are functions before
wrapping them. This prevents TypeError when calling the wrapped function
if the original method was undefined during configuration.

Update tests to verify that missing methods remain undefined rather than
being wrapped with an undefined reference.

Co-authored-by: Claude <noreply@anthropic.com>
When `videopress/video` is not in `allowed_block_types`, initialize the
VideoPress AJAX bridge to handle `core/video` blocks extended to rely
upon VideoPress upload services. AJAX auth is always initialized.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dcalhoun and others added 17 commits March 26, 2026 13:06
WordPress's `lodash-js-after` inline script calls `_.noConflict()` to
restore `window._` to Underscore.js. Since GutenbergKit excludes core
WordPress assets from the editor assets endpoint but doesn't load
Underscore, this wipes `window._` to `undefined`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GutenbergKit excludes core WordPress assets from the editor assets
endpoint, so wp-util.js (which provides wp.ajax and wp.template) must
be vendored and loaded directly. Load it via dynamic import at the end
of initializeWordPressGlobals() after jQuery and lodash are on window,
since its IIFE captures jQuery via closure at execution time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The wp.ajax.send and wp.ajax.post wrappers accepted a single options
argument, but wp-util's implementation accepts (action, options). Align
the wrapper signatures so the action argument is forwarded correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use `homeUrlString()` instead of `siteUrlString()` from the REST API
root response. The `url` field often returns `http://` for WordPress.com
sites, while `home` returns the actual public-facing `https://` URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WordPress core sets these aliases in media-models.js, which
GutenbergKit doesn't load. Alias them after auth wrapping so media
uploads use the authenticated AJAX methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoid including latest changes from the WordPress/wordpress-develop
repository.
Replace jQuery.ajaxSetup and wp.ajax.send/post wrappers with a single
jQuery.ajaxPrefilter that only injects the Authorization header when
the request URL starts with the configured siteURL. This prevents
leaking credentials to cross-origin requests and avoids argument
normalization issues with the previous wp.ajax wrapper approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents double-slash in constructed URLs (e.g.,
`https://example.com//wp-admin/admin-ajax.php`) when siteURL is
provided with a trailing slash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…JAX auth

The ajaxPrefilter silently no-ops via optional chaining when jQuery is
missing, but the debug log still claims auth was configured. Guard with
an early return and warning so the log accurately reflects what happened.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace `startsWith(siteURL)` with `URL.origin` comparison so that
scheme, host, and port must all match exactly. This prevents credential
leakage to lookalike domains (e.g. `https://example.com.evil.com`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the iOS and Android code examples out of the Android-specific
requirement so they are not visually nested under that bullet point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
configureAjax() now initializes wp.ajax, wp.ajax.settings, and the AJAX
URL before the VideoPress bridge runs, making the duplicate setup in
initializeVideoPressAjaxBridge() unnecessary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap `new URL(siteURL)` in try/catch so a malformed siteURL logs a
  warning instead of throwing.
- Guard `configureMediaAjax` against missing `wp.ajax.send`/`post`
  (e.g., if wp-util.js failed to load).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Throw IllegalArgumentException if the value contains a scheme, path, or
is blank, so callers get a clear error instead of a malformed asset URL
at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Record the upstream commit hash and rationale for vendoring so future
maintainers know where the file came from and when to update it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Account.WpCom.username stores just the hostname (e.g.,
"dcpaid.wordpress.com") since it is extracted via URI.host during OAuth.
ConfigurationItem was using this bare hostname as siteUrl, producing
invalid AJAX endpoints. Prepend "https://" to match the self-hosted
flow, which receives a full URL from the callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dcalhoun dcalhoun force-pushed the feat/authorize-ajax-with-application-passwords branch from c09e90a to 282c499 Compare March 26, 2026 17:08
dcalhoun and others added 2 commits March 26, 2026 13:11
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dcalhoun dcalhoun marked this pull request as ready for review March 26, 2026 17:24
return EditorConfigurationBuilder(
postType: selectedPostTypeDetails,
siteURL: URL(string: apiRoot.siteUrlString())!,
siteURL: URL(string: apiRoot.homeUrlString())!,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ensures WP.com sites use TLS for the siteURL configuration in the demo app.

Comment on lines +147 to +151
if ( ! allowedBlockTypes?.includes( 'videopress/video' ) ) {
// The VideoPress block isn't available, so initialize the bridge to handle
// any `core/video` blocks extended to rely upon VideoPress upload services.
initializeVideoPressAjaxBridge();
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Retained to continue support for WP.com core/video blocks that are extended to rely upon VideoPress upload services. This can be removed in the future once videopress/video support is enabled and deemed stable.

accountId = account.id,
name = account.username,
siteUrl = account.username,
siteUrl = "https://${account.username}",
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The account.username is protocol-less, which causes errors as a GBK.siteURL value. It is safe to assume TLS for WP.com sites.

Comment on lines -30 to -38
// Initialize wp.ajax if not already present
window.wp.ajax = window.wp.ajax || {};
window.wp.ajax.settings = window.wp.ajax.settings || {};

// Set up AJAX settings with site URL
const { siteURL } = getGBKit();
if ( siteURL ) {
window.wp.ajax.settings.url = `${ siteURL }/wp-admin/admin-ajax.php`;
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Now redundant of the AJAX-specific configuration that runs before this bridge.

@dcalhoun dcalhoun requested a review from kean March 26, 2026 17:24
Derive the WebViewAssetLoader domain from the configured siteURL
instead of defaulting to the synthetic appassets.androidplatform.net
domain. This makes REST API and admin-ajax.php requests same-origin,
eliminating CORS restrictions without requiring server-side headers.

- Restrict shouldOverrideUrlLoading to /assets/ paths on the asset
  domain so arbitrary site pages don't load inside the WebView.
- Reorder shouldInterceptRequest to check the cache interceptor
  before the asset loader, preventing cached JS/CSS from being
  short-circuited when both share the site domain.
- Remove the now-unnecessary assetLoaderDomain configuration option
  from EditorConfiguration.
- Update AJAX documentation to reflect the simplified setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dcalhoun dcalhoun requested a review from nbradbury March 28, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant