cloudflare / cloudflare/cloudflare-docs

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

Open
#30,076 4 comments 0 reactions 6 assignees Claimed by @WalshyDev View on GitHub
product:workers stale
Dominant language
MDX
Stars
5.2k
Forks
16.7k
Avg merge
2d 6h
Merged PRs (30d)
337

Description

## 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:///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](https://blog.cloudflare.com/building-automatic-platform-optimization-for-wordpress-using-cloudflare-workers/)). That Worker uses `fetch()` subrequests against the origin, and the [default of `fetch()` in Cloudflare Workers is `redirect: "follow"`](https://developers.cloudflare.com/workers/runtime-apis/request/). Each hop is a new subrequest that does not carry cookies from the prior hop's response.

Related known behavior: [cloudflare/miniflare#133](https://github.com/cloudflare/miniflare/issues/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

- https://community.cloudflare.com/t/apo-cache-bypass-with-worker-routes-possible-regression/773362 — confirmed APO + Worker Routes regression, CF staff acknowledged
- https://community.cloudflare.com/t/using-apo-with-workers/213638 — design-level incompatibility
- https://community.cloudflare.com/t/requests-with-a-worker-enabled-always-miss-on-apo-behavior-change/261989 — behavior change confirmed
- https://community.cloudflare.com/t/apo-origin-cookies-always-bypass/533712 — APO + cookie behavior
- https://github.com/cloudflare/miniflare/issues/133 — same runtime pattern in miniflare

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.