How it works
We use Cloudflare Access (part of Cloudflare Zero Trust) to gate our hosted docs and client spaces. When someone visits a gated subdomain, Cloudflare intercepts the request at the edge and shows a login screen before the page ever loads. No code changes needed per site.
The Zero Trust dashboard is at:
dash.cloudflare.com/.../access-controls/apps
Team domain: mangroveweb.cloudflareaccess.com
Login methods
Two identity providers are configured (Settings > Authentication > Login methods):
| Method | Who uses it | Notes |
|---|---|---|
| Google OAuth | Mangrove team (@mangrove-web.com) |
Connected to a Google Cloud project ("Mangrove CF Access"). Redirect URI: https://mangroveweb.cloudflareaccess.com/cdn-cgi/access/callback |
| One-time PIN (email) | Firefly, clients, anyone without Google Workspace | Built into Cloudflare Access by default. User enters their email, gets a code, enters it. No setup needed per person. |
Microsoft/Azure AD is not connected today — Firefly folks and clients use the email one-time PIN. To add either provider, follow the steps below.
Setting up a login method
All identity providers are added under Zero Trust > Settings > Authentication > Login methods > Add new. Every provider uses the same callback URL: https://mangroveweb.cloudflareaccess.com/cdn-cgi/access/callback.
Google (Workspace / OAuth)
- Google Cloud Console > the "Mangrove CF Access" project > APIs & Services > Credentials > Create credentials > OAuth client ID > Web application.
- Add the callback URL above as an Authorized redirect URI.
- Copy the Client ID and Client Secret.
- In Zero Trust, add a Google login method and paste them. Save, then Test.
Microsoft (Entra ID / Azure AD)
- Entra admin center > App registrations > New registration.
- Add the callback URL above as a Web redirect URI.
- Copy the Application (client) ID and Directory (tenant) ID.
- Certificates & secrets > New client secret > copy the secret value.
- API permissions > Microsoft Graph (delegated):
openid,email,profile,User.Read> grant admin consent. - In Zero Trust, add an Azure AD login method > paste the client ID, secret, and tenant ID. Save, then Test.
Once added, each Access application's policy can require a specific identity provider or allow all of them. External folks (Firefly, clients) can still use the email one-time PIN.
Access tiers
Each Access application has a policy that controls who gets in. We use two patterns:
| Tier | Policy rule | Who |
|---|---|---|
| Mangrove only | Emails ending in @mangrove-web.com |
Internal docs, the hub index, tools |
| Mangrove + Firefly | Emails ending in @mangrove-web.com + @fireflypartners.com |
Joint client spaces, shared docs |
To gate something for a specific client, add their individual email addresses to the policy's include rules (not a domain rule, since clients don't share a domain).
Current Access applications
As of Sep 25, 2026:
| Application name | Destination | What it is | Policy |
|---|---|---|---|
| docs | docs.mangrove-web.com | Internal Mangrove Team index of all published Mangrove docs, proposals, and client links | Mangrove Team |
| maiyadocs | maiyadocs.mangrove-web.com | Maiya's dochub | Maiya only |
| mgff | mgff.mangrove-web.com | Firefly x Mangrove shared workspace (estimates, resourcing, shared docs map) | Mangrove + Firefly |
| madeforgood | madeforgood.mangrove-web.com | Made for Good venture docs (Maiya + Jen, data hub, ecosystem maps) | Mangrove + Firefly |
| regardingher | regardingher.mangrove-web.com | Regarding Her Foods (RHF) client space | Mangrove + Firefly |
Update this table when you add or remove applications.
Client subdomains: two-tier auth pattern
Each client subdomain should use two Access applications to separate team-only docs from client-shared docs:
| Path pattern | Policy | Who sees it |
|---|---|---|
subdomain.mangrove-web.com/* | Mangrove + Firefly | Team only (everything by default: project plans, budgets, internal notes) |
subdomain.mangrove-web.com/client/* | Mangrove + Firefly + client contacts | Team + the client (deliverables, status, shared docs) |
subdomain.madeforgood.ai/* | Mangrove + Firefly | Team only — same pattern on the Made for Good domain |
subdomain.madeforgood.ai/client/* | Mangrove + Firefly + client contacts | Team + the client |
The same two-tier pattern applies on both mangrove-web.com and madeforgood.ai subdomains — one Access app locking the whole subdomain to the team, a second opening /client/* to client contacts.
The more specific path wins. The base policy locks the entire subdomain to the team. Only /client/* opens up to client contacts. Everything outside /client/ stays team-only.
To add client contacts: edit the /client/* app's policy and add their individual emails as include rules. They log in via the email one-time PIN.
Domains left open (no gate)
| Domain | Why it's open |
|---|---|
proposals.mangrove-web.com | Clients access their proposals here. Gating it locks them out. |
prod2026.mangrove-web.com | Public Mangrove website. |
annual25.mangrove-web.com | Public Mangrove website. |
claudetraining.mangrove-web.com | Training sandbox, shared openly. |
Client .pages.dev domains
Client projects on *.pages.dev (e.g. kids-in-need-of-defense.pages.dev, rhf-artifacts.pages.dev) are not gated by Access. They rely on unguessable token paths for security.
To gate a .pages.dev domain, you have two options:
- Assign a custom subdomain under
mangrove-web.com(e.g.kind.mangrove-web.com) and create an Access application for it. The custom domain must have its DNS record set to Proxied (orange cloud) for Access to intercept traffic. - Create an individual Access application for the
.pages.devdomain directly. This works but requires one app per project since you can't wildcard*.pages.dev(Cloudflare owns that domain, not you).
Adding a new Worker/Pages project
Client spaces, deliverables, internal docs, and the marketing site are all Cloudflare Pages projects. Two ways to deploy one:
| Method | When to use it |
|---|---|
| Git-connect (default) | Connect the GitHub repo in the Pages dashboard. Every push to the production branch builds and deploys automatically. Preferred for anything with an ongoing life. |
Direct upload (wrangler pages deploy, or a repo's publish.sh) | Move-fast fallback, or a project with no git connection. You deploy from your machine; nothing rebuilds on push. |
Build output directory: published.
On a git-connected project, set Settings > Builds & deployments > Build output directory to published — not the repo root. Our builds write the finished site into published/. Leaving it at the default root deploys the site one level deep, so the clean URL keeps serving the old copy — the classic "I pushed but it's not updating."
A new project first comes up on <project>.pages.dev. To move it onto <name>.mangrove-web.com, add the custom domain under Pages > the project > Custom domains (its DNS record must be Proxied, the orange cloud). Then decide how it's gated: gate it with Access (see adding a new Access application below), or leave it open on an unguessable token path — the same trade-off as the client .pages.dev domains above.
How to add a new Access application
- Go to Zero Trust > Access controls > Applications
- Click + Create new application
- Select Self-hosted and private, click Continue
- Set the Application name (use the subdomain, e.g. "kind")
- Under Destinations, enter the full subdomain (e.g.
kind.mangrove-web.com) - Under Policies, create a policy:
- Policy name: "Mangrove Team" or "Mangrove + Firefly"
- Action: Allow
- Include: Emails ending in
@mangrove-web.com - For Firefly access: add a second include rule for
@fireflypartners.com - For client access: add individual emails
- Session duration: 24 hours is a reasonable default
- Save
DNS requirement: the subdomain's DNS record must be Proxied (orange cloud icon in DNS settings), not DNS-only (gray cloud). Access cannot intercept traffic that doesn't flow through Cloudflare's proxy.
How to add a person to a policy
To give a specific person (a client contact, a contractor) access to a gated subdomain:
- Go to the Access application for that subdomain
- Edit the policy
- Add an include rule: Emails > enter their exact email address
- Save
They'll use the email one-time PIN to log in (no Google account needed).
Removing access
To remove a person: edit the policy and delete their email from the include rules.
To un-gate a subdomain entirely: delete the Access application. The site becomes open immediately.
To revoke an active session: go to Insights & Logs > Access > Active Users and revoke their session.
Middleware (legacy)
The doc hub (docs.mangrove-web.com) previously used HTTP Basic Auth via a Cloudflare Pages Function (platform/functions/_middleware.js) with a HUB_PASSWORD environment secret. That password gate was removed on Sep 25, 2026 when Cloudflare Access was set up.
The middleware still exists but only handles:
- Routing published docs via the
PUBLISHEDset (injected bydeploy.sh) - Returning 404 for unknown paths (prevents the index.html fallback from leaking the index)
- Per-doc password gates in the
PROTECTEDmap (currently empty)
Troubleshooting
A subdomain isn't showing the Access login
Check that the DNS record is Proxied (orange cloud), not DNS-only. Go to the main Cloudflare dashboard > mangrove-web.com zone > DNS > Records, find the subdomain, and toggle proxy on.
Someone can't log in
Confirm their email is in the policy's include rules (exact match). If they're using Google and it fails, the email one-time PIN is the fallback. They just enter their email on the Access login screen.
Google OAuth stops working
The OAuth credentials live in a Google Cloud project ("Mangrove CF Access"). Check that the client secret hasn't expired and the redirect URI is still https://mangroveweb.cloudflareaccess.com/cdn-cgi/access/callback.
"Authentication required" browser popup (not the Access login page)
That's the old HTTP Basic Auth, not Cloudflare Access. It means the middleware still has the password gate for that route. Check platform/functions/_middleware.js and the HUB_PASSWORD Pages secret.