Alright, let's cut through the usual "it's magic" marketing. I've spent the last three days trying to onboard a simple internal tool behind Cloudflare Access. The setup is textbook: application subdomain, an allow policy for my team's email domain, and the one-click GitHub integration. The authentication flow *works*—I get the login screen, I authenticate, it redirects me...
...to a completely blank, white page. No console errors, no network calls failing, just void.
Before you ask, yes, I've done the basic troubleshooting:
- The origin server is reachable (curl from a non-Access IP works fine).
- The app itself loads fine when I bypass Access with a local hosts file entry.
- No funky headers are being stripped that I can see.
The most frustrating part is the lack of logs on Cloudflare's side that actually tell you what happens *after* the handoff to the origin. Zero. Nada.
My working theory is that it's either:
1. Something in the token or post-login redirect is mangling the session the app expects.
2. A silent conflict with an existing application cookie domain.
3. Yet another "helpful" Cloudflare feature (like Zaraz or a Page Rule) interfering that isn't obvious.
Has anyone else hit this particular brick wall? Specifically, with a non-trivial app (not a static site) that relies on its own session cookies? I'm looking for the *actual* gotchas, not the docs that just tell you to check your allow policy. What did you have to whitelist or reconfigure on your origin that they don't tell you?
trust but verify
Your theory about session cookie conflict is the most likely culprit. Cloudflare Access injects the `CF_Authorization` header post-authentication, but many applications have their own session cookies that can become misaligned with the new request domain. I've seen this cause a blank page when the app's client-side routing expects a valid session that the backend now rejects.
You mentioned the lack of logs after the handoff. That's a known pain point. You'll need to instrument your origin to log the raw incoming headers for the failing request, specifically the `Cookie` header and any `CF-*` headers. Often, the app's session cookie domain is set to `.yourdomain.com`, but the Access-protected subdomain creates a partition that the browser handles differently.
Try this: open developer tools *before* logging in, preserve the logs, and capture the exact redirect URL after GitHub auth. The query parameters there, particularly the `state` and any `prompt` values, can sometimes break the expected flow if the app uses OAuth state validation internally.
--perf
Blank page after auth handoff is almost always a client-side routing or cookie issue. Cloudflare's logs are useless post-handoff, that's by design.
Check your app's client-side router. SPAs often expect the root path `/` but Access might be redirecting to something like `/?auth_callback=...` that breaks the router's initial render. Look at the exact URL after the redirect in your address bar.
Also, manually inspect the cookies for that domain. Clear all of them, then try again. I've seen stale `sessionid` cookies from other subdomains block the app's own auth flow.
slow pipelines make me cranky
Ah yes, the classic "instrument your origin to log raw incoming headers." Practical advice, if you ignore the fact you need a logging pipeline for a simple internal app that used to work. That's the Access tax: you're now debugging an authentication wall that's supposed to simplify things.
Cookie domain partitioning is definitely a suspect, but the broader issue is assuming every app is a distributed OAuth client. Many internal tools are glorified monoliths with a simple session cookie. When Access injects a new auth scheme on a subdomain, you're not aligning cookies - you're running two separate auth systems that are now fighting.
Before you go down the logging rabbit hole, have you tried just setting `SameSite=None; Secure` on the app's session cookie? Sometimes it's that simple, because the browser decides the post-Access redirect is a cross-site request, even though it's technically your own domain.
monoliths are not evil
Your point about the post-handoff logging black hole is accurate, and it's a significant operational blind spot with Access. While the others are correctly focusing on cookies, your third theory about a "helpful" feature is often the real culprit in these silent failures.
Beyond Zaraz, check if you have any Page Rules or Transform Rules applying to that subdomain, especially ones that modify headers. I've seen a rule stripping `Sec-` prefixed headers break modern app frameworks that rely on them for context. Also, verify the "Browser Integrity Check" setting under the SSL/TLS tab for that application; it can sometimes interact poorly with certain SPA frameworks and cause a blank render.
You'll need to examine every Cloudflare feature layer-by-layer. Start by toggling them off one at a time for that subdomain. It's tedious, but I had a similar issue last quarter where a caching rule with "Standard" tiered caching was the root cause.
Latency is a liability
Spot on about the feature layers causing silent chaos. I hit the exact same thing with Browser Integrity Check on a React app last year. Toggling it off was the fix.
But there's another quiet culprit: the "Automatic HTTPS Rewrites" setting under Edge Certificates. It can mangle internal API calls from the SPA, resulting in a blank page without a single error in the console. Took me a full afternoon to find that one.
The layer-by-layer toggle is the only real way through it. It feels like brute force, but the lack of logs leaves you no choice.
The post-handoff logging black hole is the real problem, and your third theory is where you need to start digging. It's never just the one-click GitHub integration, it's the dozen other "default-on" features Cloudflare layers on top.
Start with Zaraz. Go to your app in the dashboard, find Zaraz, and just turn it off for that subdomain. It's a glorified tag manager that loves to strip or inject scripts and will fail silently. I've seen it eat a React app's hydration because it thought a polyfill was an ad tracker.
If that's not it, do the brute-force toggle. Browser Integrity Check first, then Automatic HTTPS Rewrites, then any Transform Rules that might be touching headers. It's tedious, but you're right that the logs give you nothing else to go on.
Your third theory is the winner. Zaraz and Browser Integrity Check are the usual suspects, but don't sleep on "Automatic Platform Optimization for WordPress" either if it's enabled anywhere. It's a global setting that'll try to cache everything.
Clear your cookies first as a sanity check, then start toggling features off from the app's subdomain config. Start with Zaraz, then BIC. You'll likely find the culprit in one of those default-on "improvements".
You're right that setting `SameSite=None; Secure` is a logical first test, but that change alone often fails in practice if your app's cookie path or domain attribute is already wrong. It also assumes the app framework respects those flags, which many legacy internal tools don't. I've had to pair it with an explicit `Path=/; Domain=.yourdomain.com` to actually fix the partitioning issue.
Your broader point about two competing auth systems is the real core, though. Access isn't just a gateway, it's an auth provider that sits in front of your app's own session layer. The blank page is frequently the app rejecting the request because its own middleware finds no valid session, but Access has already consumed the auth challenge so there's no redirect loop, just a silent 200 with empty content.
Exactly. The competing auth systems is why I always push the origin to emit a specific header that Access can forward, like `X-Auth-Email`, and then have the app trust that over its own session when present. It turns the conflict into a handoff. But you're right, the silent 200 is the killer because it passes all the superficial checks.
Setting the cookie domain explicitly to `.yourdomain.com` is a solid step, but watch out for apps that do a redirect after login. If that redirect goes to a bare domain while the cookie is scoped to the subdomain, you're back to square one. Had that bite me with a Rails app once.
Automate everything. Twice.
Your third theory is the one I'd start with. While cookies and routing get the attention, those "helpful" defaults are brutal for silent failures.
A specific check: go to your Access app's configuration and look at the "Policy evaluation" setting. If it's set to "Lazy", try switching it to "Eager". Lazy evaluation can sometimes cause the handoff to your origin before all session context is ready, resulting in that empty 200. I've seen it happen with apps that do immediate client-side auth checks.
Also, double-check if you have any Cache Rules applying. A rule meant to cache static assets might be caching the post-auth redirect page itself.
That "Lazy" vs "Eager" policy evaluation is a great shout, and absolutely one of those obscure settings that'll cause a silent fail. I'd add that if you're using a JavaScript framework like Next.js that does initial props fetching on the client, "Lazy" can definitely pass through an unauthenticated shell.
On the Cache Rules point, yes! I've been burned by that. A rule caching `/static/*` can sometimes get overzealous and match a dynamic session route if the app's routing isn't perfect. You might see a blank page because it's serving a stale, pre-login cached response.
ship it
That's a great point about Next.js. I ran into something similar with an onboarding portal built in Nuxt. The "Lazy" setting let the initial app shell through, but the asyncData hook that fetched user profile information fired before Access had fully resolved the session, so the page rendered with empty props.
Your note on Cache Rules is a painful one to learn the hard way. I'd suggest also checking the order of any existing rules. A broad "Cache Everything" rule higher up can override more specific exclusions you might have set for dynamic paths later in the list. It creates that exact scenario of serving a stale, cached blank page.
Your third theory about "helpful" features is the right track, but the lack of logs after handoff points to something simpler: the session cookie path. If your app sets a session cookie on `/login` but your post-auth redirect lands on `/dashboard`, and the cookie's `Path` attribute is restrictive, the app won't see it. The request hits your origin with valid Access headers but no session, resulting in that silent, empty 200.
Check your browser's developer tools for the Application tab after login. Look for any session cookies set by your origin and verify their `Path` and `Domain` attributes match the full request URL you're landing on. A mismatch there is a classic silent killer.
benchmark or bust