Skip to content

feat: AJAX uses token authentication#181

Draft
dcalhoun wants to merge 10 commits intotrunkfrom
feat/authorize-ajax-with-application-passwords
Draft

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

Conversation

@dcalhoun
Copy link
Member

@dcalhoun dcalhoun commented Sep 19, 2025

Related:

What?

Authenticate AJAX requests with application passwords send via an authorization
header.

Why?

The GutenbergKit editor does not have authorization cookies, so we must rely
upon a different authorization mechanism.

Ref CMM-713. Close CMM-768.

How?

Set the Authorization header via jQuery.ajaxSetup and by overloading the
window.wp.ajax utilities.

Testing Instructions

  1. Apply the required Jetpack changes and WPCOM (192611-ghe-Automattic/wpcom) changes to your site.
  2. Proxy your test device's networking through to your modified WPCOM in step 1.
  3. Open the experimental block editor in the Jetpack mobile app.
  4. Insert a VideoPress block.
  5. Attach media.
  6. Verify the upload succeeds.

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
Member Author

@claude

@claude
Copy link

claude bot commented Jan 13, 2026

Claude finished @dcalhoun's task in 4m 47s —— View job


Code Review Complete ✅

I've completed a comprehensive review of PR #181. Overall, this is a well-implemented and thoroughly tested feature that successfully replaces the VideoPress-specific bridge with a general AJAX authentication solution. The code quality is high, security is properly handled, and the documentation is excellent.


Summary

This PR introduces token-based authentication for AJAX requests via authorization headers, replacing the previous VideoPress-specific bridge (videopress-bridge.js) with a more general and robust solution (ajax.js). The implementation properly configures both jQuery AJAX and WordPress AJAX utilities (wp.ajax.send and wp.ajax.post) to inject authentication headers.

Key Changes:

  • ✅ New ajax.js module with comprehensive AJAX configuration
  • ✅ Excellent test coverage (491 lines of tests)
  • ✅ Android configuration updated to support authHeader and assetLoaderDomain
  • ✅ Comprehensive documentation for AJAX requirements
  • ✅ Removed VideoPress-specific bridge in favor of general solution

Detailed Findings

1. Code Quality ⭐⭐⭐⭐⭐

Strengths:

  • Clean, readable code with clear function separation
  • Excellent use of optional chaining (window.jQuery?.ajaxSetup)
  • Proper error handling with warning logs
  • Good function ordering following repository conventions
  • Well-structured with helper functions (configureAjaxUrl, configureAjaxAuth)

Minor Observations:

  • The code correctly preserves existing beforeSend callbacks when wrapping wp.ajax.send and wp.ajax.post
  • Defensive programming with checks for missing siteURL and authHeader
  • Uses project's logger utility instead of console methods ✅

2. Security ⭐⭐⭐⭐⭐

Strengths:

  • ✅ Authorization headers properly injected into all AJAX requests
  • ✅ No credential exposure in logs (tokens not logged)
  • ✅ Android properly validates and passes authHeader through configuration
  • ✅ Token authentication is more secure than cookie-based auth for native WebViews
  • ✅ CORS properly documented and configured for Android's assetLoaderDomain

Observations:

  • The authHeader is expected to be pre-formatted (e.g., "Bearer token") - this is appropriate since token formatting is the caller's responsibility
  • Authorization headers are set via both jQuery global setup and per-request injection for wp.ajax methods, ensuring comprehensive coverage

3. Performance ⭐⭐⭐⭐⭐

Strengths:

  • ✅ Minimal overhead - configuration happens once at initialization
  • ✅ No unnecessary network requests or polling
  • ✅ Function wrapping uses closures efficiently
  • ✅ Proper use of optional chaining avoids unnecessary checks

Observations:

  • The wrapping of wp.ajax.send and wp.ajax.post adds one additional function call per request, which is negligible
  • Configuration is called once in initializeEditor (src/utils/editor-environment.js:141)

4. Testing ⭐⭐⭐⭐⭐

Strengths:

  • Exceptional test coverage with 491 lines of tests
  • ✅ Tests cover all scenarios:
    • URL configuration with and without siteURL
    • Auth configuration with and without authHeader
    • jQuery integration
    • wp.ajax.send and wp.ajax.post wrapping
    • Preservation of original beforeSend callbacks
    • Edge cases (missing jQuery, missing methods, partial initialization)
    • Integration scenarios
  • ✅ Uses proper mocking with Vitest
  • ✅ Tests verify both success and error paths
  • ✅ Good test organization with descriptive test names

Observations:

  • Test coverage appears comprehensive based on the implementation
  • Tests properly verify that authorization headers are set correctly
  • Tests verify that existing beforeSend callbacks are preserved

5. Documentation ⭐⭐⭐⭐⭐

Strengths:

  • Excellent documentation in docs/integration.md covering:
    • Requirements for AJAX support
    • Production bundle requirement (CORS limitations)
    • Platform-specific configuration differences clearly explained
    • Server-side CORS configuration notes
  • ✅ Troubleshooting guide updated with AJAX CORS error documentation
  • ✅ Code comments explain the purpose and behavior clearly
  • ✅ JSDoc comments for public functions

Observations:

  • The documentation correctly notes that development server doesn't support AJAX due to CORS
  • Android-specific assetLoaderDomain requirement is well-documented
  • Documentation provides concrete code examples for both iOS and Android

Issues Found

Critical Issues

None

High Priority Issues

None

Medium Priority Issues

1. Potential Race Condition with wp.ajax Methods

Location: src/utils/ajax.js:49-77

The code wraps window.wp.ajax.send and window.wp.ajax.post, but these methods might not exist at the time configureAjax() is called (lines 49-77). While the tests verify this scenario doesn't crash during configuration, the code doesn't check if the methods exist before wrapping them.

Current behavior:

const originalSend = window.wp.ajax.send; // Could be undefined
window.wp.ajax.send = function(options) {
    // ...
    return originalSend.call(this, options); // Will fail if originalSend is undefined
};

Impact: If window.wp.ajax.send is undefined when configureAjax() runs, the wrapped function will work fine until it's actually invoked, at which point it will throw TypeError: originalSend.call is not a function.

Recommendation: Add existence checks before wrapping:

if (typeof window.wp.ajax.send === 'function') {
    const originalSend = window.wp.ajax.send;
    window.wp.ajax.send = function(options) { ... };
}

Test Gap: Test on line 390 verifies this doesn't throw during configuration, but doesn't test what happens when the wrapped method is actually invoked with originalSend being undefined.

See inline comment: src/utils/ajax.js:49-61

Low Priority Issues

2. Redundant Authorization Header Setting

Location: src/utils/ajax.js:43-47, 49-77

The code sets authorization headers via jQuery.ajaxSetup() AND by wrapping wp.ajax.send/wp.ajax.post. For wp.ajax methods, this means the authorization header is set twice.

Impact: Minimal - setting the same header twice is harmless. This provides defense-in-depth and ensures the auth header is present even if one mechanism fails.

Recommendation: This is fine as-is. The redundancy is actually beneficial for robustness.

See inline comment: src/utils/ajax.js:43-47


Positive Highlights 🌟

Excellent Architecture

  • Replacing the VideoPress-specific bridge with a general AJAX solution is architecturally superior
  • Enables any WordPress blocks/plugins that use AJAX to work in GutenbergKit
  • Clean separation of concerns between URL configuration and auth configuration

Outstanding Test Coverage

  • 491 lines of comprehensive tests covering all edge cases
  • Tests for missing jQuery, undefined methods, partial initialization
  • Integration tests verify the complete flow
  • See inline comment: src/utils/ajax.test.js:355-491

Great Android Integration

Comprehensive Documentation

  • Clear explanation of AJAX requirements and limitations
  • Platform-specific differences well-documented
  • Troubleshooting guide includes AJAX-specific errors
  • Code examples provided for both iOS and Android

Recommendations

Must Fix Before Merge

None - the code is production-ready as-is. The medium-priority issue is an edge case that may never occur in practice.

Should Consider

  1. ⚠️ Add existence checks before wrapping wp.ajax.send and wp.ajax.post methods (see inline comment)
  2. Add test for wrapped method invocation when original method is undefined

Nice to Have

  1. Document the timing of AJAX configuration in code comments
  2. Add comment explaining redundant header setting is intentional for defense in depth
  3. Consider adding a reconfigureAjax() function for token updates (if needed in future)

Conclusion

This is a high-quality PR that successfully implements token authentication for AJAX requests. The code is well-written, thoroughly tested, and properly documented. The architectural decision to replace the VideoPress-specific bridge with a general solution is excellent.

Scores

  • Code Quality: ⭐⭐⭐⭐⭐ 5/5
  • Security: ⭐⭐⭐⭐⭐ 5/5
  • Performance: ⭐⭐⭐⭐⭐ 5/5
  • Testing: ⭐⭐⭐⭐⭐ 5/5
  • Documentation: ⭐⭐⭐⭐⭐ 5/5

Recommendation

Approve with minor suggestions - The identified issues are minor and don't block merging. The PR is production-ready, and the suggestions above would make the code slightly more robust but aren't critical for functionality.

The only medium-priority issue (wrapping potentially undefined methods) is an edge case that may never occur in practice since WordPress globals are loaded before AJAX configuration runs.


Files Reviewed

  • src/utils/ajax.js - Core AJAX configuration
  • src/utils/ajax.test.js - Comprehensive test suite (491 lines)
  • src/utils/editor-environment.js - Integration point
  • src/utils/editor-environment.test.js - Integration tests
  • android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorConfiguration.kt - Android config
  • android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt - Android WebView
  • docs/integration.md - AJAX documentation (40 new lines)
  • docs/code/troubleshooting.md - AJAX troubleshooting
  • src/utils/videopress-bridge.js - Deleted (replaced by ajax.js)
  • ios/Sources/GutenbergKit/Sources/Model/GBKitGlobal.swift - iOS configuration (verified authHeader support)

| Branch: feat/authorize-ajax-with-application-passwords

dcalhoun and others added 10 commits February 12, 2026 12:22
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>
@dcalhoun dcalhoun force-pushed the feat/authorize-ajax-with-application-passwords branch from 394bce2 to ea73b3c Compare February 12, 2026 17:50
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

Comments