WordPress Plugins
Pricing Blog Switch to Royal Plugin Graveyard Support My Account Cart
Support / Royal MCP / PKCE Parameters Missing at /authorize

PKCE Parameters Missing at /authorize — Host WAF Strips OAuth Query Args

If a ChatGPT, Claude, or other MCP client connection fails at the OAuth authorize step with invalid_request: PKCE with code_challenge_method=S256 is required, and your Royal MCP Activity Log shows the /authorize request arriving with response_type, client_id, and redirect_uri but no code_challenge or code_challenge_method, your host’s Web Application Firewall is stripping those two query parameters before the request reaches PHP. This is common on managed shared hosts running ModSecurity with OWASP Core Rule Set defaults. The fix lives at the hosting layer, not in Royal MCP.

Before applying these advanced steps

Royal MCP OAuth connection issues that look host-specific are usually a plugin conflict, cache layer, or stale OAuth state, and about 90% resolve in our 4-step basic checklist. If you haven’t run through that yet, do it first. This article assumes the basics are already ruled out and your Activity Log is showing the raw request with the two PKCE parameters absent.

Most Common Hosts Affected

Managed shared hosting running ModSecurity with OWASP Core Rule Set defaults, especially SiteGround where a common OWASP CRS rule matches argument names ending in _method as HTTP verb-override protection and cascades onto code_challenge_method. Also seen on other cPanel-based shared / reseller hosts running similar CRS defaults. Premium managed WordPress hosts with dedicated OAuth-aware WAF policies (WP Engine, Kinsta, Pressable) are typically not affected.

Before You Apply the Fix — Confirm This Is Your Issue

An invalid_request at /authorize has more than one possible cause. The Activity Log check below tells you specifically whether the two PKCE parameters were absent when the request reached PHP. If the picture doesn’t match what’s in this article, the basic checklist routes you to the right fix.

This article applies to you if…

  • You’ve already run the 4-step basic checklist without resolving it
  • Your Royal MCP Activity Log shows an oauth:authorize row for your failed connection attempt (not empty, so this isn’t ModSecurity 406 or a /.well-known/ block)
  • The Activity Log row’s Request Data → uri field contains response_type, client_id, redirect_uri, scope, and state but is missing both code_challenge and code_challenge_method
  • The Response Data shows {"http_status": 302, "code": "invalid_request", "description": "PKCE with code_challenge_method=S256 is required."}
  • Your Royal MCP OAuth Dynamic Client Registration (/register) is working correctly and returning HTTP 201 with a valid client_id
  • Your hosting stack is a managed shared host running ModSecurity (SiteGround, InMotion, A2 Hosting, HostGator, GreenGeeks, or any cPanel-based host running OWASP CRS defaults)

Have these on hand before you start

You’ll need these to apply the fix below, and they’re what our support team needs if you end up emailing afterward:

  • Your exact hosting provider (per-host support channels vary)
  • A copy of the Activity Log → oauth:authorize row with Request Data expanded (redact IPs, client IDs, and state values)
  • Your Royal MCP version from WP Admin → Plugins (1.4.16 or newer for the diagnostic-quality Activity Log entries that capture raw REQUEST_URI)
  • Approximate timestamps of two or three failed attempts, so your host’s support team can pull matching WAF audit-log entries

Symptoms

You probably have this issue if all of the following are true:

The Telltale Signs

  • OAuth Dynamic Client Registration succeeds: /register returns HTTP 201, Royal MCP records oauth:register as SUCCESS in the Activity Log, and the client receives a valid client_id
  • The next step, /authorize, fails immediately with {"http_status": 302, "code": "invalid_request", "description": "PKCE with code_challenge_method=S256 is required."}
  • The Activity Log’s oauth:authorize row is present, with a Request Data uri field that includes every OAuth parameter except code_challenge and code_challenge_method
  • The failure is reproducible across two or more connection attempts
  • Both ChatGPT and Claude reach the same failure state, because both send the OAuth 2.1 PKCE parameters identically. If one MCP client works and the other fails, the cause is somewhere else
Why the Activity Log entry is the definitive signal

Royal MCP’s Activity Log records the raw $_SERVER['REQUEST_URI'] that WordPress receives from the web server, before Royal MCP parses it into $_GET. If code_challenge and code_challenge_method are absent from that raw uri field, they were absent when the request arrived at PHP. Nothing in Royal MCP’s code path strips or transforms those parameters, so the missing pieces have to be somewhere between the AI client and your web server.

How to Verify It’s This Specific Issue

Two quick checks confirm the diagnosis.

Confirm the Activity Log row shows the parameters absent

Open WordPress Admin → Royal MCP → Activity Log. Find the most recent oauth:authorize row for your failed connection attempt. Expand View Details to see Request Data. The uri field is the raw request line as PHP received it. A stripped request looks like this (redacted):

{
  "method": "GET",
  "uri": "/authorize?response_type=code&client_id=[REDACTED]&redirect_uri=[REDACTED]&scope=mcp:full&resource=[REDACTED]&state=[REDACTED]&ui_locales=es-ES",
  "ip": "[REDACTED]",
  "user_agent": "Mozilla/5.0 ...",
  "client_id": "[REDACTED]",
  "grant_type": "",
  "response_type": "code"
}

Neither code_challenge= nor code_challenge_method= appears in the uri. That confirms the strip happened upstream of PHP.

Rule out an MCP-client-side skip

Both ChatGPT and Claude send PKCE parameters on /authorize. If you have only tested with one, try the other, then check the fresh Activity Log row. If both clients produce the same shape (parameters absent, same error), the strip is at your host layer and applies to every MCP client. If one client succeeds where the other fails, the strip is targeting something specific to one client’s request shape, which is a narrower fix scope.

Why your OAuth registration works but authorize fails

The /register endpoint uses a POST with a JSON body. WAF rules that inspect query arguments by name don’t affect JSON bodies. The /authorize endpoint uses a GET with everything in the query string, which is exactly where argument-name filters operate. That’s why registration passes cleanly while authorization gets its critical parameters stripped.

The Fix — Ask Your Host to Exclude the OAuth Paths from WAF Argument-Name Filters

Royal MCP can’t un-strip parameters that never reached PHP, so the fix has to happen at the WAF layer. Send your host’s support team the message below. Most hosts respond within a few hours and apply the exclusion as a path-based rule adjustment.

Option A: Open a support ticket with your host

Copy this into your host support ticket, chat, or email. Fill in your domain in place of example.com:

Hi support team,

OAuth 2.1 authorization requests to my site are failing because
code_challenge and code_challenge_method are being stripped from
the query string on GET /authorize before requests reach PHP.

The request path is:

  https://example.com/authorize?response_type=code&client_id=…
  &redirect_uri=…&scope=…&state=…
  &code_challenge=…&code_challenge_method=S256

WordPress receives everything EXCEPT the last two parameters. This
matches the pattern of a ModSecurity or OWASP Core Rule Set rule
matching argument names ending in _method as HTTP verb-override
protection. The rule inadvertently strips code_challenge_method
(and often code_challenge with it as a cascade match).

Could you please:

  1. Pull the ModSecurity audit log entries for GET /authorize
     from my IP over the last 24 hours and identify which rule
     is matching or transforming those two arguments.

  2. Test-reproduce internally by running (single-line URL):

       curl -v "https://example.com/authorize?response_type=code&client_id=test&redirect_uri=https://example.com&code_challenge=abc123&code_challenge_method=S256&state=x&scope=mcp:full"

     from one of your servers and verify whether all query
     parameters traverse the proxy layer to PHP intact.

  3. Apply a path-based WAF rule exclusion for the following
     paths so the two PKCE parameters are allowed through:

       /authorize
       /register
       /token
       /wp-json/royal-mcp/*
       /.well-known/oauth-authorization-server
       /.well-known/oauth-protected-resource

Reference: code_challenge and code_challenge_method are required
by RFC 7636 (Proof Key for Code Exchange, PKCE), a mandatory
security extension for OAuth 2.1 authorization code flows used by
modern AI clients (ChatGPT, Claude, Perplexity). Stripping them
blocks the entire OAuth 2.1 flow, not just this one plugin.

Thank you!

Two things worth stressing in the ticket if it gets pushed back:

Option B: Per-host quick notes

Specific to the hosts where we’ve confirmed this issue:

SiteGround

SiteGround runs ModSecurity with OWASP CRS defaults at the edge. Their support team is generally cooperative on OAuth-related WAF exclusions once given the exact paths. Open a chat ticket from your Site Tools and paste the Option A text. Ask them specifically to look for any active OWASP CRS rule matching argument names ending in _method, and to test-reproduce with the internal curl above. Once they identify the rule, they typically apply the exclusion server-side within an hour.

If you’re on SiteGround and this is the second WAF fix you’ve needed for MCP (the first was likely a python-httpx User-Agent rule that was blocking /register), that’s a different WAF layer. Mention both when opening the ticket so their team applies the same exclusion pattern consistently.

InMotion / A2 Hosting / HostGator / GreenGeeks / Namecheap shared

All resell variants of the same cPanel + ModSecurity + Comodo CWAF or OWASP CRS stack. Some plans expose ModSecurity in cPanel under Security → ModSecurity, but the argument-name rule is usually a global CRS rule that’s not toggleable from the per-domain UI. Open a support ticket with the Option A text. Bluehost specifically doesn’t expose ModSec in cPanel at all, so ticket is the only path there.

Cloudflare in front of your host

If your site is on Cloudflare, verify their WAF isn’t the source before asking your host: in the Cloudflare dashboard, open Security → Events and filter for the /authorize path around the timestamp of a failed attempt. If Cloudflare shows a “Managed Rule” or “Custom Rule” matching the request, add a Skip rule for the OAuth paths there first, then re-test. If Cloudflare shows the request passing through cleanly, the strip is happening at your origin host.

Why Does This Happen?

ModSecurity is a generic web-application firewall that ships with cPanel and is enabled by default on most managed shared hosting. The OWASP Core Rule Set (CRS) is the most common ruleset bundled with it. Among the CRS rules is a family that inspects argument names (not values) for patterns associated with common attack vectors.

One such pattern is HTTP method override. Some web frameworks (Ruby on Rails, Symfony, older PHP frameworks) use a query argument named _method to override the request’s HTTP verb, so a GET can be treated as a DELETE if the URL contains ?_method=DELETE. Unfiltered _method arguments have been used in attacks. CRS ships a rule that matches argument names ending in _method and either strips them, logs them, or blocks the whole request depending on the rule’s anomaly-scoring configuration.

The OAuth 2.1 PKCE specification (RFC 7636) defines the argument name code_challenge_method. This name ends in _method. On hosts running CRS with the HTTP-method-override rule at strict-strip mode, code_challenge_method matches the pattern and gets stripped from the query string. In some rule configurations, the neighbouring argument code_challenge is also affected because the two are treated as a cascade set, or because a separate rule matches code_challenge* prefixes and reacts to whatever else the first rule flagged. The net effect: both parameters disappear before the request reaches WordPress, and Royal MCP returns invalid_request because PKCE is mandatory under OAuth 2.1 with public clients.

This is a generic security policy, not anything specific to Royal MCP. The same rule blocks any web application that uses OAuth 2.1 PKCE, which is now the industry-standard OAuth flow for public clients: it’s used by every modern AI assistant, every mobile app, every SPA doing authorization code flow. As OAuth 2.1 adoption grows, more managed hosts are updating their default CRS rulesets to whitelist code_challenge* argument names, but plenty of hosts still run older configurations.

Can we fix this from the plugin side instead?

No. Royal MCP can’t recover parameters that never reached PHP. By the time WordPress’s parse_request action fires, $_SERVER['REQUEST_URI'] and $_GET already reflect whatever the WAF let through. The strip happens at the reverse-proxy or edge layer, above the application entirely. Fixing it has to happen at the WAF configuration layer, which is why the fix in Option A above is a support ticket to your host rather than a plugin setting.

What Changes After the Fix

Once your host applies the path-based WAF exclusion, retry the connection from your AI client:

Reproduce the connection attempt

From your MCP client (ChatGPT, Claude, or whichever failed originally), start a fresh connection to your Royal MCP endpoint. The OAuth flow should now complete: /register returns 201, the browser opens for consent, you approve, and the client shows the connection as active.

Confirm the Activity Log shows PKCE parameters intact

Open WordPress Admin → Royal MCP → Activity Log and look at the new oauth:authorize row. Expand Request Data. The uri field should now include both code_challenge=… and code_challenge_method=S256 alongside the other OAuth parameters. Response Data should show {"http_status": 302, "code": "success"} or similar successful redirect state.

If the connection still fails, the strip may be at more than one layer

Some hosts run WAF at both edge (Cloudflare or their own reverse proxy) and origin (ModSecurity on the web server). If one layer is fixed but the other isn’t, the parameters can still be stripped. Check the Activity Log again: if code_challenge is now present but code_challenge_method is still missing, or vice versa, you’re dealing with two separate rules. Go back to your host with the follow-up ask.

If This Doesn’t Resolve It

The path-based WAF exclusion resolves the vast majority of PKCE-stripping cases. If your host has applied the exclusion and the Activity Log still shows the two parameters absent, one of the following is happening:

The exclusion didn’t take effect

Some hosts require a service restart or a cache purge before rule changes take effect. Ask your host to confirm the rule is active on your specific domain (some multi-tenant WAF configurations apply per-host, not per-server). Ask them to re-run the internal curl reproducer from Option A and verify all parameters traverse the proxy layer.

A second WAF layer is stripping the same parameters

If your site sits behind Cloudflare in addition to your host’s WAF, both layers can be stripping arguments. Fix Cloudflare first, then your host, then re-test. Cloudflare’s Security → Events log will tell you if a Managed Rule or Custom Rule matched the request; if so, add a WAF Skip rule for the OAuth paths in Cloudflare.

It’s a different failure mode that looks similar

Not every “OAuth authorize fails” case is PKCE stripping. The OAuth Fails on Managed Hosts article covers edge-cache poisoning of OAuth endpoints (a different symptom shape, with cache-hit headers on /authorize). The SiteGround /.well-known/ 404 article covers OAuth discovery failing at the metadata step (also a different symptom shape, empty Activity Log rather than PKCE-stripped rows). If your symptoms match one of those articles better than this one, follow that path instead.

Contact our support team

If none of the above applies and you’ve confirmed the strip is happening at the host layer, open a support ticket via the Support portal or the Royal MCP forum on WordPress.org. Include the information listed in the “Have these on hand” card at the top of this article, plus a copy of the specific rule ID your host identified (if they gave you one).

Related host-side OAuth failures

If your Activity Log picture doesn’t match PKCE-stripping specifically, check the adjacent failure modes: