Skip to content

APO follows origin 301/302 redirects internally and drops Set-Cookie, breaking auth flows #30076

@teknovision

Description

@teknovision

Summary

The Cloudflare edge follows same-origin 301/302 redirects from the origin internally and returns the end-of-chain 200 body to the client. Set-Cookie headers set by the first hop are dropped because the internal subrequest to the redirect target does not replay cookies from the prior hop. This breaks any login flow that relies on Set-Cookie + Location in a single 302 response (e.g., WordPress one-time login tokens, passwordless auth plugins, any SSO handshake that issues 302 after setting an auth cookie).

Behavior is not documented anywhere in the Cloudflare Automatic Platform Optimization (APO), Cache Rules, or Workers docs.

Scope

  • Zone: Pro plan
  • WordPress installed under a /wordpress/ subdirectory (not at root)
  • APO installed on zone (wp_plugin: true, wordpress: true). Setting enabled: false and hostnames: [] via API does not remove the APO Worker from the zone routing — responses still carry cf-apo-via: origin,page-rules after the toggle.
  • Observed on every origin 301/302 to the same hostname, not just auth paths

Reproduction

Direct curl to the origin IP (bypassing Cloudflare) returns the correct 302 with Set-Cookie and Location:

$ curl -skI https://<origin-ip>/wordpress/wp-login.php?wtlwp_token=HASH -H "Host: example.com"
HTTP/2 302
set-cookie: wordpress_sec_...=...
set-cookie: wordpress_logged_in_...=...
cache-control: no-store, no-cache, must-revalidate, private
cf-edge-cache: no-cache
location: https://example.com/wordpress/wp-admin/

Through Cloudflare returns 200 with the final-destination body, no auth cookies, no Location:

$ curl -sI https://example.com/wordpress/wp-login.php?wtlwp_token=HASH
HTTP/2 200
cf-cache-status: MISS
cf-apo-via: origin,page-rules
cache-control: no-store, must-revalidate, private
[45 KB of login form HTML]

Nginx access log for that single client request shows three sequential subrequests from the same Cloudflare edge IP, 1 second apart, each chasing the previous Location header:

162.158.217.84  [11:58:20]  GET /wordpress/wp-login.php?wtlwp_token=HASH                             302
162.158.217.84  [11:58:21]  GET /wordpress/wp-admin/                                                  302
162.158.217.84  [11:58:22]  GET /wordpress/wp-login.php?redirect_to=%2Fwp-admin%2F&reauth=1           200

The Set-Cookie headers from hop 1 are not replayed on the hop 2 request, so /wp-admin/ authenticates as unauthenticated and bounces back to the login form.

Confirmed not the cause (ruled out)

  • APO automatic_platform_optimization settings: tried enabled: false, wordpress: false, wp_plugin: false, hostnames: []cf-apo-via header persists in responses
  • Cache Rule with cache: false + browser_ttl: {mode: bypass} on the affected paths
  • Transform Rule forcing Cache-Control: no-store, no-cache, must-revalidate, private, max-age=0
  • Zone settings: development_mode: on, always_online: off, speed_brain: off, prefetch_preload: off, browser_cache_ttl: 0
  • Workers Routes on the zone (empty)
  • Redirect Rules / Page Rules / Config Rules (no match on affected paths)
  • Origin headers verified clean via direct IP curl

What I believe is happening

APO is architected as a Cloudflare Worker that runs on every edge request for the zone (blog). That Worker uses fetch() subrequests against the origin, and the default of fetch() in Cloudflare Workers is redirect: "follow". Each hop is a new subrequest that does not carry cookies from the prior hop's response.

Related known behavior: cloudflare/miniflare#133 — Miniflare's fetch internally resolves 302 and strips Set-Cookie. Same pattern.

The APO Worker route appears to persist on the zone even after automatic_platform_optimization.enabled: false is set via API. The cf-apo-via header only appears on zones where APO has been installed.

What the docs should clarify

  1. That APO's Worker intercepts all edge requests for an installed zone and remains installed after toggling enabled: false — and how to fully remove it (currently requires a support ticket per community reports).
  2. That APO follows origin 301/302 redirects internally and returns the end-of-chain 200 body to the client, dropping intermediate Set-Cookie headers — this breaks any auth flow that sets a cookie on a 302 response.
  3. That this makes APO incompatible with WordPress plugins that use wp_set_auth_cookie() followed by wp_redirect() — including the popular "Temporary Login Without Password" plugin, Magic Login plugins, Jetpack SSO, and any passwordless/one-time-token auth flow.
  4. Workarounds or how to scope APO to skip admin/login paths.

References

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions