Skip to content

Authentication

Authentication is handled entirely by Cloudflare Access at the edge. No OAuth code, login pages, or session management exists in our codebase.

  1. User visits app.dotcollective.com.au
  2. Cloudflare Access intercepts and shows Google Workspace login
  3. Only @dotcollective.com.au emails are allowed (domain restriction)
  4. After auth, Cloudflare injects a signed JWT via the Cf-Access-Jwt-Assertion header and CF_Authorization cookie
  5. Our Worker validates this JWT against the team’s certs endpoint (checks header first, falls back to cookie)
  6. User email and name are extracted from the JWT and upserted in D1 (name only on first login — user-set names are preserved)
  • Google OAuth flow, token exchange, session management
  • Login UI and redirect flows
  • Domain restriction enforcement (@dotcollective.com.au)
  • Session cookie management and expiry
  • OIDC claims forwarding (including Google profile picture via picture claim)
  • JWT validation (worker/lib/cf-access.ts) - Validates against https://dotcollective.cloudflareaccess.com/cdn-cgi/access/certs
  • User management - Auto-creates users in D1 on first login, preserves user edits on subsequent visits
  • Profile picture sync - On first login, fetches Google profile picture from /cdn-cgi/access/get-identity OIDC claims and saves to D1
  • Access levels - Assigns roles (Executive, Head, Manager, Lead, Employee)
  • Tool permissions - Per-tool view/update/manage grants
  • Frontend auth context - Fetches /api/users/me for UI permissions

The Google IdP in Cloudflare Zero Trust must have picture added as an OIDC claim. This makes the Google profile photo URL available in the /cdn-cgi/access/get-identity response under oidc_fields.picture.

Users can edit their display name at /profile. The name is preserved across logins (the auth middleware only sets the name on initial user creation, not on subsequent visits).

Cloudflare Access is not present locally. The app provides a dev user picker at localhost:5173:

  1. On load, GET /api/dev/users fetches all existing users (unauthenticated endpoint)
  2. The picker shows existing users with avatars and access level badges
  3. Selecting a user (or entering a new email) stores the email in localStorage
  4. All subsequent API calls include a CF-Access-Mock-Email header via the apiFetch wrapper
  5. The auth middleware accepts this header and impersonates the user
  6. “Switch user” in the user menu clears the selection and returns to the picker