Gating a Self-Hosted App With Cloudflare Access + Google Login
putting a real login in front of a self-hosted app that has weak or no auth of its own — Cloudflare Access as the gate, Google as the identity provider, and deciding which apps actually need it.
once a self-hosted app is reachable from the internet — via a cloudflare tunnel or anything else — its own login screen is the only thing between the world and your data. for some apps that’s fine. for others it’s a basic-auth box or nothing at all.
Cloudflare Access sits in front of the app and makes you authenticate to Cloudflare before a request ever reaches it. this walks through wiring Google in as the identity provider and gating one hostname with it.
first: does this app actually need a gate?
not every exposed service does. a rough split:
| tier | what it looks like | gate? |
|---|---|---|
| never expose | infra + credential-holding stuff — your CA, DNS admin, monitoring, container managers, anything holding API keys | not exposed at all, LAN-only |
| tier 1 | app has a real account system built for remote use — proper password + optional 2FA | no gate — its own auth is enough |
| tier 2 | app is worth reaching remotely but has weak auth, a shared password, or none | Access gate — Cloudflare does the authenticating |
the gate is for tier 2. putting one in front of a tier-1 app isn’t wrong, just redundant. the point of the exercise is that a compromised login on a document store or a finance app is a much worse day than on a bookmark manager — spend the gate where the blast radius justifies it.
step 1 — find your Zero Trust team domain
Cloudflare Zero Trust → Settings → Custom Pages (or Settings → General) → Team domain. it looks like <team-name>.cloudflareaccess.com. Zero Trust Free assigns a random one if you’ve never set it — that’s fine, just note it, you need it for the redirect URI next.
step 2 — create a Google OAuth client
in the Google Cloud Console:
- create or select a project
- APIs & Services → OAuth consent screen (first time only):
- User type / Audience: External
- App name: anything
- User support email + developer contact: your own email
- leave publishing status at Testing if you’re the only person who’ll ever log in — no verification needed
- APIs & Services → Credentials → Create Credentials → OAuth client ID:
- Application type: Web application
- Authorized redirect URI:
https://<team-name>.cloudflareaccess.com/cdn-cgi/access/callback
- it hands you a Client ID and a Client Secret — the secret is shown once, copy it now
the redirect URI has to be exactOAuth error at login time, not at setup time
step 3 — add Google as a login method in Cloudflare
Zero Trust → Access controls → Login methods (it’s under Access controls, not under Settings — this trips people up). Add new → Google (plain Google, not “Google Workspace” — that one is for G Suite organisational domains).
| field | value |
|---|---|
| Name | Google |
| App ID | the Client ID from step 2 |
| Client secret | the Client Secret from step 2 |
| Email claim name | (leave blank) |
the 'email claim' field wants a claim name, not your emailbrowser autofill will get this wrong for you
save, then hit Test on the login method — it should bounce you through Google and back with a green result.
step 4 — create the Access application
Zero Trust → Access controls → Applications → Add an application → Self-hosted.
- Application domain: the hostname you’re gating — subdomain
app, domainyourdomain.com - add a policy underneath it:
- Action: Allow
- Rules → Include → selector Emails → the specific address(es) allowed in
Emails alone is enough — since login already goes through Google, matching the email address ties the policy to that Google account. you don’t also need a separate “login methods” rule unless you want to restrict which IdP, and you’ve only wired one.
step 5 — verify the gate actually gates
curl -sI https://app.yourdomain.com/
expect a 302 redirecting to https://<team-name>.cloudflareaccess.com/... — the Access login. if you get the app’s own response instead, the application domain doesn’t match, or the app is also reachable on a second hostname that isn’t listed.
then do it for real in an incognito window: you should hit Google, approve, and land on the app. a browser that’s already authenticated to Cloudflare Access skips straight through — that’s expected, not a bypass.
Access only protects the exact hostnames it's told toa second hostname on the same app is wide open
the app 400s behind the gateHost-header allowlist, unrelated to Access
what this does and doesn’t buy you
it means no unauthenticated request reaches the app — good against credential-stuffing its own login, against an unauthenticated RCE in the app, against someone just finding the hostname. it does not replace keeping the app updated, and it does not help if the allowed Google account itself is compromised. it’s a layer, sized to the tier-2 case: worth reaching remotely, not worth trusting the app’s own front door alone.