Locking the site down
The site is public to anyone who has the URL. Two ways to close it, depending
on whether you want an email code or a password you can hand to someone.
Option A — Cloudflare Access (email PIN)
No password to remember or leak. You enter your email, Cloudflare mails a
one-time code, you’re in for the session length you set. Free up to 50 users.
Setup
- Cloudflare dashboard → Workers & Pages → your project → Settings →
General → find Access policy → Enable. - That opens Zero Trust and pre-fills an application. First time through it
asks for a team name — this becomes your login domain
(your-team.cloudflareaccess.com). Pick something you’ll recognise. - Under Policies, set Action to Allow, then Include →
Emails → your address. - Set Session duration. 24 hours or a week is sensible; you’re re-auth’ing
on every device otherwise. - Create.
The part that catches people
!!! warning “The default only covers previews”
The auto-created application uses a wildcard subdomain (*), which
protects preview deployments like a1b2c3.shan-network-blog.pages.dev —
not your production shan-network-blog.pages.dev.
To cover production, go to Zero Trust → **Access** → **Applications** and
either edit the application's hostname to the bare production domain, or
add a second application for it.
Verify
Open the site in an incognito window. You should get Cloudflare’s email
prompt, not your notes. Testing in your normal browser proves nothing — you
already have a session.
If the PIN email doesn’t arrive, check spam, then confirm the email in the
policy matches exactly.
Option B — A shared password
Cloudflare Pages has no native password option, but a Pages Function can do
HTTP Basic Auth in about twenty lines. Use this if you want to hand the URL and
a password to someone without adding them to an access list.
1. Create the function
At the repo root (not inside docs/):
mkdir functions
code functions/_middleware.js
_middleware.js runs before every request. Paste:
export const onRequest = async ({ request, env, next }) => {
const header = request.headers.get("Authorization");
if (header) {
const [scheme, encoded] = header.split(" ");
if (scheme === "Basic" && encoded) {
const decoded = atob(encoded);
const split = decoded.indexOf(":");
const user = decoded.slice(0, split);
const pass = decoded.slice(split + 1);
if (user === env.SITE_USER && pass === env.SITE_PASSWORD) {
return next();
}
}
}
return new Response("Authentication required.", {
status: 401,
headers: {
"WWW-Authenticate": 'Basic realm="notes", charset="UTF-8"',
},
});
};
2. Set the credentials
Cloudflare dashboard → project → Settings → Variables and Secrets →
add two, as Secret (encrypted), not plaintext:
| Name | Value |
|---|---|
SITE_USER |
whatever you like |
SITE_PASSWORD |
a long random string |
Add them to Production. Add to Preview too if you want previews covered.
3. Fail closed
In the project’s Functions settings, set the behaviour to fail closed.
If the function can’t run — daily request limit, an error — failing open would
serve the site unprotected. Failing closed serves an error instead. Wrong
answer is better than no lock.
4. Deploy and verify
git add functions/
git commit -m "feat: basic auth"
git push
Wait for the build, then open the site in an incognito window. You should
get the browser’s own username/password dialog.
Caveats
- Credentials go over HTTPS, so they’re encrypted in transit — but Basic Auth
sends them on every request, and the browser caches them until you close it.
There’s no logout button. - Anyone with the password has full access. Rotating it means editing the
secret and redeploying. - Never put the password in the repo. That’s what the secrets are for.
Which to pick
| Access | Basic Auth | |
|---|---|---|
| Setup | Dashboard only | 20 lines + 2 secrets |
| Login | Email PIN | Username + password |
| Sharing | Add each person’s email | Hand over the password |
| Revoking | Remove the email | Change the password for everyone |
| Runs on | Cloudflare’s edge auth | Your Pages Function |
Just you: Access — nothing to leak, nothing to maintain.
A study group: Basic Auth — one credential, no list to manage.
Reverting
Access: Zero Trust → Access → Applications → delete the application. Effective
immediately.
Basic Auth: delete functions/_middleware.js, commit, push. Live after the
next build.