# Cloudflare Access (Zero Trust) Setup ## Overview Cloudflare Access sits in front of subdomains and checks user identity before traffic reaches the origin server. Provides SSO and 2FA for any self-hosted service without server-side auth code. ## When to Use - Replace Caddy basic_auth for internal pages - Add 2FA layer over existing login systems (TwentyCRM magic link, DocuSeal) - Unify auth across multiple services (CRM, internal dashboard, DocuSeal) - Protect any self-hosted web app without modifying the app itself ## Prerequisites - Cloudflare API token with **Access: Apps and Policies: Edit** permission - Existing DNS-only token can be updated (Edit → Add permission, then regenerate) - Subdomains must be proxied through Cloudflare (orange cloud) for Access to work ## Setup via API ### Account - Account ID: from `GET /client/v4/accounts` - Token: with Access: Apps and Policies: Edit permission ### Create an Access Application ```bash curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"App Name","domain":"subdomain.yourdomain.com","type":"self_hosted","session_duration":"24h"}' ``` ### Add a Policy to an Application ```bash APP_ID=$(curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps" ... | jq '.result[] | select(.name=="App Name").id') # Policy with email domain include (no 2FA requirement yet) curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps/$APP_ID/policies" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Team","decision":"allow","include":[{"email_domain":{"domain":"germainebrown.com"}}]}' ``` ### Policy Types - `include` — who is ALLOWED (email, email_domain, everyone, etc.) - `require` — what is REQUIRED (totp, mfa, etc.) - `exclude` — who is BLOCKED ### Known Issues - `require: [{totp: {}}]` returned `invalid 'require' configuration` in testing (Jul 7, 2026). May need to configure 2FA at the Zero Trust org level first, or use the dashboard UI. - 2FA/one-time passcode is a "require" rule but may need to be set via the Cloudflare dashboard rather than API for initial setup. - Session duration of "24h" means users re-authenticate daily. Adjust based on security needs. ## Dashboard Setup (Fallback) If API fails: 1. Login at https://one.dash.cloudflare.com/ 2. Access → Applications → Add application → Self-hosted 3. Enter subdomain, session duration 4. Add policy: name, include (email or email_domain), require (2FA if needed) 5. Save ## DNS Requirement - Subdomains covered by Access must be proxied (orange cloud) in Cloudflare DNS - This enables Cloudflare to intercept requests before they reach the origin - Non-proxied (gray cloud) records bypass Access entirely ## Removing Caddy Basic Auth After Access is Working Once Access is confirmed working, remove the `basic_auth` block from the Caddyfile or the old auth section: ```bash # In Caddyfile: remove @internal handle block and basic_auth # Reload: caddy reload --config /etc/caddy/Caddyfile ```