This document describes intentional behavior of the Cloudflare site Worker that backs the admin SPA (cloudflare_site/worker/), especially CORS for GET /api/github/user and why there is no separate “admin OAuth enabled” boolean in configuration.
For local setup and env vars, see web/admin/README.md. For CI and deploy layout, see docs/site-deployment.md.
Most admin /api/* traffic is evaluated with a single effective browser origin derived from the Origin header (loopback dev, or same origin as the Worker in production). Same-origin fetch from the SPA normally sends Origin.
Some same-origin or dev-proxy patterns can omit Origin on GET /api/github/user (and on its OPTIONS preflight): for example certain same-origin fetches or proxies in front of Wrangler/Vite. Without a reflected Access-Control-Allow-Origin, the browser would block the response even though the request is harmless from a token-exchange perspective: this route is Bearer-only (no OAuth code, no client_secret, no session cookie minted by the Worker).
For this path only, when Origin is absent, the Worker may still compute Access-Control-Allow-Origin by combining:
- Fetch Metadata:
Sec-Fetch-Sitemust besame-originorsame-site(browser signal that the initiating context is not cross-site in the usual sense). Referer: the request’sReferermust parse to an origin that matches the Worker’s public origin, or a paired loopback rule (both Worker URL and referer origin are loopback HTTP, for typical Vite + Wrangler port splits).
This inference is not used for:
POST /api/oauth/token— tab binding requiresOriginpresent and equal to theredirect_uriorigin (fetchTabBindingOkinoauthCors.ts). Missing or mismatchedOriginyields 403forbidden_origin.GET /api/oauth/authorize— usesOriginwhen present; without it, the Worker uses the navigation rule (redirect_uriallowlist + same Worker origin or loopback pairing), not theSec-Fetch-Site+Refererpath used for/api/github/user.
Using Referer (even gated on Sec-Fetch-Site) is weaker than Origin for reflecting CORS: it is not treated as proof of who the caller is. It is an accepted, intentional trade-off only to keep the GitHub /user proxy usable when Origin is missing, while keeping token exchange strict.
Explicitly: Referer is not used to authenticate or authorize the GitHub token exchange. Exchange still requires a valid Origin, PKCE, Turnstile verification, redirect_uri allowlist, GitHub code + app credentials, and rate limits (see below).
Successful responses are not a verbatim pass-through of GitHub’s /user JSON. The Worker returns only login, name, and avatar_url (all other GitHub fields are stripped) so the browser receives the smallest profile the admin UI needs, including for follow-on features that display avatars.
Admin OAuth is not gated on a separate boolean environment variable (“feature off”). Instead, misconfiguration or absence of required secrets naturally prevents the surface from working or from being meaningfully exploitable:
- Turnstile: both
TURNSTILE_SITE_KEYandTURNSTILE_SECRET_KEYmust be non-empty for any admin/api/oauth/*or/api/github/userhandling. If either is missing or blank, the Worker responds with 503 and JSONerror: "server_misconfigured",detail: "missing_turnstile_keys". That is the operational “off” story: there is no silent “Turnstile disabled” mode for these routes. - GitHub App:
POST /api/oauth/tokenrequiresGITHUB_APP_CLIENT_IDandGITHUB_APP_CLIENT_SECRET(non-empty); otherwise the Worker returns 500server_misconfigured. The SPA does not embedclient_secret; exchange runs only in the Worker. - Rate limits: Wrangler native rate limits apply per path and client IP (e.g. token exchange vs.
/api/github/user), reducing abuse volume. - Origin rules on exchange:
POST /api/oauth/tokenrequiresOriginmatching theredirect_uriorigin;redirect_urimust be on the allowlist (HTTPS or loopback,/admin/callback paths). - Authorize:
GET /api/oauth/authorizeenforcesredirect_uriallowlist and tab/navigation binding (authorizeTabBindingOk); Turnstile site key is folded intostateserver-side.
Together, these layers define the safety model without a second “master switch” that could be forgotten left-on in production or flipped without audit.