Cloudflare Access With Free OAuth — Protect Your Site Without Google Workspace
I run this blog on Cloudflare Pages — static HTML, no server, no backend. But there are things I want to put behind authentication: admin dashboards, internal tools, staging previews. The conventional answer is “set up Google Workspace SSO.” That costs money, requires domain verification, and ties you to a paid Google tenant.
It turns out you do not need any of that. Cloudflare Access works with free Google OAuth — a standard Google Cloud Console OAuth Client ID, connected to a plain Gmail account. No Workspace. No domain verification. No cost. You can also use GitHub OAuth, which is even simpler.
Here is how to set it up, end to end.
Part 1: The Architecture
Three things happen:
Cloudflare intercepts the request before it reaches your origin. If there is no valid
CF_Authorizationcookie, the user sees the Cloudflare Access login page instead of your site.The user authenticates against Google or GitHub. Cloudflare never sees their password — it just receives an OAuth authorization code and exchanges it for an identity token. The IdP handles all the credential verification.
Cloudflare evaluates your Access policy. If the user’s email domain matches, or they belong to the right GitHub organization, Cloudflare mints a signed JWT, sets it as a cookie, and forwards the request to your origin. The origin receives a
Cf-Access-Jwt-Assertionheader with the validated user identity.
Your origin does not need to implement authentication. Cloudflare does it at the edge, before any byte of your application is served.
Part 2: Google OAuth — Free, No Workspace
This is the path that surprises people. You do not need Google Workspace. You do not need a verified domain. You just need a Google Cloud project with an OAuth consent screen configured for External audience.
Step 1: Create a Google Cloud Project
Go to the Google Cloud Console. Create a new project. Name it whatever you want — it is just an internal label.
Step 2: Configure the OAuth Consent Screen
Navigate to APIs & Services → OAuth consent screen. Choose External as the user type. This is the critical setting — “External” means any Google account can authenticate, not just accounts in your Workspace organization.
Fill in the required fields:
| Field | Value |
|---|---|
| App name | Your site name (visible to users on the consent screen) |
| User support email | Your Gmail address |
| Developer contact | Your Gmail address |
| Scopes | email, profile, openid
(default) |
Do not add sensitive scopes. The default
email/profile/openid scopes do
not require Google verification. If you add scopes like
calendar.readonly or drive.file, Google will
require you to verify your app — a process that can take weeks.
Save and continue. On the “Test users” step, add your own Gmail address as a test user while the app is in testing mode. You can publish it later, but testing mode is sufficient for your own use.
Step 3: Create the OAuth Client ID
Go to APIs & Services → Credentials → Create Credentials → OAuth client ID. Choose Web application.
Two fields matter:
| Field | Value |
|---|---|
| Authorized JavaScript origins | https://<your-team-name>.cloudflareaccess.com |
| Authorized redirect URIs | https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/callback |
Your team name is visible in the Cloudflare Zero Trust dashboard
under Settings → Team Domain. It looks like
myteam.cloudflareaccess.com.
Copy the Client ID and Client Secret. You will paste them into Cloudflare.
Step 4: Add Google as an Identity Provider in Cloudflare
In the Cloudflare dashboard, go to Zero Trust → Settings → Authentication → Identity Providers → Add new → Google.
Paste your Client ID and Client Secret. Click Save. That is it.
Common Pitfall: “Access blocked: app not verified”
If you see this during login, your OAuth consent screen is in testing mode and the Google account you are logging in with is not on the test users list. Go back to OAuth consent screen → Test users and add the email address.
Alternatively, click Publish App on the consent screen to make it available to all Google users. Publishing does not require verification as long as you only use the default scopes.
Part 3: GitHub OAuth — Even Simpler
If your audience is developers, GitHub OAuth is the path of least resistance. No cloud console, no consent screen, no test users. Just an OAuth App registration.
Step 1: Register a GitHub OAuth App
Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.
| Field | Value |
|---|---|
| Application name | Your site name |
| Homepage URL | https://<your-team-name>.cloudflareaccess.com |
| Authorization callback URL | https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/callback |
Register the app. Copy the Client ID. Click Generate a new client secret and copy it.
Step 2: Add GitHub as an Identity Provider in Cloudflare
Zero Trust → Settings → Authentication → Identity Providers → Add new → GitHub. Paste the Client ID and Client Secret. Save.
That is the entire setup. GitHub OAuth has zero configuration beyond those two fields.
What GitHub Claims Are Available
When a user authenticates via GitHub, Cloudflare Access receives these claims from the GitHub OAuth token:
| Claim | Example | Use In Policy |
|---|---|---|
username |
nurazhardotcom |
Allow specific users |
email |
[email protected] |
Allow by email |
organizations |
my-org |
Allow org members |
teams |
my-org/engineering |
Allow team members |
These claims become available as Access policy selectors. You can
write a policy that says “Allow if GitHub organization is
my-company” — and only members of that GitHub org get
through.
Part 4: Writing Access Policies
An Access policy is a set of rules that determine who gets in. Every policy has an Action (Allow, Block, or Bypass), one or more Rules (Include, Require, Exclude), and Selectors that match against identity provider claims.
Policy Examples
Allow specific email addresses:
Action: Allow
Include → Selector: Emails → Value: [email protected], [email protected]
Allow an entire email domain (free Gmail):
Action: Allow
Include → Selector: Emails ending in → Value: @gmail.com
Allow a GitHub organization:
Action: Allow
Include → Selector: GitHub Organization → Value: my-company
Allow GitHub org members OR specific email:
Action: Allow
Include → Selector: GitHub Organization → Value: my-company
--- OR ---
Include → Selector: Emails → Value: [email protected]
Require both a specific email domain AND a specific GitHub org (defense in depth):
Action: Allow
Include → Selector: Emails ending in → Value: @my-company.com
Require → Selector: GitHub Organization → Value: my-company
The difference between Include and Require: - Include rules are OR’d — satisfy any one to pass - Require rules are AND’d — must satisfy ALL Require rules in addition to at least one Include rule
Multiple Identity Providers
You can enable both Google and GitHub simultaneously. When a user hits the login page, they see both options:
┌─────────────────────────────┐
│ Cloudflare Access │
│ │
│ [Login with Google] │
│ [Login with GitHub] │
│ │
└─────────────────────────────┘
Policies can reference claims from either IdP. A user who logs in via Google must satisfy the Google-based rules. A user who logs in via GitHub must satisfy the GitHub-based rules. You can mix and match freely.
Part 5: Protecting Your Site
For Cloudflare Pages
If your site is hosted on Cloudflare Pages (like
nurazhar.com):
- Go to Zero Trust → Access → Applications → Add an application
- Choose Self-hosted
- Set the application domain to your Pages custom domain (e.g.,
admin.nurazhar.com) - Attach your Access policy
- Done
The first visitor to admin.nurazhar.com sees the
Cloudflare Access login page instead of your site content. After
authentication, they get the CF_Authorization cookie and
subsequent requests pass through transparently.
Important: Cloudflare Pages serves your site from
the edge. There is no origin server to validate JWTs against. The Access
policy enforcement happens entirely at the Cloudflare edge — your static
HTML files never know authentication is happening. This means you cannot
use the Cf-Access-Jwt-Assertion header for per-user
customization, since there is no backend to read it. If you need
per-user content on a static site, use Cloudflare Workers or Pages
Functions as a middleware layer.
For Self-Hosted Applications (Docker, VPS, Homelab)
If you are protecting a backend service:
- Create a Cloudflare Tunnel
(
cloudflared) pointing to your service - Create a Self-hosted Access application with the tunnel’s public hostname
- Enable Protect with Access on the tunnel configuration
The tunnel daemon validates the JWT before forwarding traffic to your origin. Requests without a valid token never reach your server — they are rejected at the tunnel level.
Verifying the JWT on Your Backend
If your backend needs to know who is authenticated, it can validate the JWT locally. Cloudflare publishes its public signing keys at:
https://<your-team-name>.cloudflareaccess.com/cdn-cgi/access/certs
A minimal Node.js verifier:
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: `https://${TEAM_DOMAIN}/cdn-cgi/access/certs`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
callback(null, key.getPublicKey());
});
}
function verifyToken(req) {
const token = req.cookies['CF_Authorization'] ||
req.headers['cf-access-jwt-assertion'];
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, {
issuer: `https://${TEAM_DOMAIN}`,
audience: APP_AUD
}, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
});
}
// Usage:
const claims = await verifyToken(req);
console.log(claims.email); // [email protected]
The decoded JWT contains:
{
"email": "[email protected]",
"name": "User Name",
"sub": "abc123",
"iss": "https://myteam.cloudflareaccess.com",
"aud": "abc123def456",
"exp": 1690000000,
"iat": 1689900000,
"identity_provider": "google",
"identity_provider_id": "abc-def-ghi"
}
Part 6: Free Tier — What You Get
Cloudflare Zero Trust’s free tier is genuinely generous:
| Feature | Free Tier | Paid (Pay-as-you-go) |
|---|---|---|
| Users | Up to 50 | Unlimited ($7/user/month) |
| Identity providers | Unlimited | Unlimited |
| Applications | Unlimited | Unlimited |
| Access policies | Unlimited | Unlimited |
| JWT signing | RS256 | RS256 |
| Log retention | 24 hours | 30 days |
| Device posture | Basic | Advanced |
| mTLS | No | Enterprise |
| Browser isolation | No | Enterprise |
For a personal blog, a small team internal tool, or a homelab dashboard, the free tier covers everything you need. Fifty users is more than enough for most small-scale deployments.
The main limitation is log retention — 24 hours is tight for security auditing. If you need longer retention, you can stream Access logs to external storage via Logpush.
Part 7: Security Considerations
What this protects against: - Random internet users accessing your internal tools - Search engine indexing of private pages - Unauthenticated API calls to protected endpoints - Credential-stuffing attacks (Google/GitHub handle that)
What this does NOT protect against: - A compromised
Google or GitHub account (enable 2FA on your IdP) - Token replay if the
JWT cookie is stolen via XSS (the cookie is HttpOnly by
default, mitigating this) - Attacks that bypass Cloudflare entirely by
hitting your origin IP directly (use Cloudflare Tunnel or IP
allowlisting)
Hardening recommendations:
Enable the Binding Cookie (
CF_Binding). This ties theCF_AuthorizationJWT to the specific browser session, preventing stolen cookies from being reused from a different device.Set session duration appropriately. The default is 24 hours. For sensitive tools, reduce to 15 minutes or less. Cloudflare Access supports configurable session durations per application.
Use
Requirerules for defense in depth. A policy that requires BOTH a specific email domain AND GitHub org membership is harder to bypass than either alone.Monitor Access logs in the Zero Trust dashboard. Look for unusual authentication patterns, repeated failures, or access from unexpected countries.
The Bottom Line
You can put enterprise-grade authentication in front of any site — static JAMstack, Docker container, homelab dashboard, internal wiki — without paying a cent. Google OAuth works with a plain Gmail account. GitHub OAuth works with a personal GitHub account. Cloudflare Access handles the JWT minting, cookie management, and policy evaluation at the edge.
The entire setup takes about 10 minutes: - 5 minutes to create the Google Cloud OAuth client or GitHub OAuth app - 2 minutes to paste credentials into Cloudflare - 3 minutes to write an Access policy
And then your site is behind authentication. For free. For up to 50 users. Forever.