Skip to content

feat(api): require Cloudflare Access on staging - #189

Closed
balebbae wants to merge 1 commit into
mainfrom
feat/staging-cf-access
Closed

balebbae wants to merge 1 commit into
mainfrom
feat/staging-cf-access

Conversation

@balebbae

@balebbae balebbae commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Staging sits behind Cloudflare Access, but Access only guards traffic that goes through Cloudflare. The service still answered directly on its run.app URL (and on Google's frontend for the mapped domain), and those routes skip Access. Staging holds a copy of prod's hacker data, so that bypass matters.

When ENV=staging, the app now checks for itself that every request came through Access:

  • Every request needs a valid Cf-Access-Jwt-Assertion, the JWT that Access adds to each request it lets through to the origin.
  • Each token is fully checked: RS256 signature against the team's published keys, aud matches the Access application, iss matches the team, and it hasn't expired.
  • Applies to every route, including the SPA, /v1/* and SuperTokens' /auth/*. The middleware runs ahead of everything else.
  • Fails closed: staging refuses to start without CF_ACCESS_TEAM_DOMAIN and CF_ACCESS_AUD.
  • Signing keys are fetched from https://<team>/cdn-cgi/access/certs, refreshed hourly, and refetched when an unknown key ID appears, since Cloudflare rotates them.
  • Prod is unaffected: the check only exists when ENV=staging.

Changes

  • cmd/api/cf_access.go: config, key fetching, token verification, middleware.
  • cmd/api/api.go: mounts the middleware first when enabled.
  • cmd/api/main.go: reads the two env vars, and on staging requires them and fetches the keys.
  • go.mod: golang-jwt/jwt/v5 and MicahParks/keyfunc/v2 become direct dependencies. Both were already in the module graph via SuperTokens.
  • .env.example: documents the two vars.
  • cmd/api/cf_access_test.go: covers a valid token, plus rejection of missing, wrong-key, wrong-aud, wrong-iss, expired, no-exp and unsigned tokens. Also covers the middleware enforced versus off, and team-domain normalization.

Deploy notes

Set these on harp-staging before merging:

  • CF_ACCESS_TEAM_DOMAIN=acmutd.cloudflareaccess.com
  • CF_ACCESS_AUD=<Application Audience (AUD) Tag of the HARP Staging Access app>

Without them, the staging revision built from this merge fails to start. Cloud Run keeps serving the previous revision, so staging stays up but doesn't update.

No migrations. Prod (ENV=prod) doesn't read these vars.

🤖 Generated with Claude Code

Access only guards traffic that goes through Cloudflare, so staging's
run.app URL skipped it. When ENV=staging, every request must now carry a
valid Cf-Access-Jwt-Assertion for the configured Access application, and
the service refuses to start without CF_ACCESS_TEAM_DOMAIN and
CF_ACCESS_AUD.
@balebbae balebbae closed this Oct 5, 2026
@balebbae
balebbae deleted the feat/staging-cf-access branch October 5, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant