feat(auth): Cloudflare Access JWT trust + SSO redirect login (#702)#1058
Merged
ToddHebebrand merged 1 commit intoJun 8, 2026
Merged
Conversation
…ion LanternOps#702) For self-hosters who front Breeze with Cloudflare Access: optionally trust a valid CF Access JWT on /auth/login to dedupe the double-login, plus a browser SSO redirect-login flow that mints a Breeze session from the CF Access identity and a logout chain that breaks the SSO redirect loop. - New: services/cfAccessJwt.ts (JWKS verify, fixed-host, AUD+iss checks), middleware/cfAccessLogin.ts, routes/auth/cfAccessRedirectLogin.ts. - /auth/login trusts a verified CF Access JWT when CF_ACCESS_TRUST_ENABLED (off by default; requires CF_ACCESS_TEAM_DOMAIN + CF_ACCESS_AUD; CF_ACCESS_TRUSTS_MFA marks the session MFA-satisfied by operator assertion). - Web: bootstrapFromCfAccessRedirect() completes the SPA handshake; LoginPage + AuthOverlay offer the SSO entry; logout chains app+team logout. - Config env + validation wired; docs page added. All example domains are generic placeholders.
ToddHebebrand
added a commit
that referenced
this pull request
Jun 10, 2026
…logout redirect (#1193) **Root cause:** Verification of the v0.69.0..main Codex review confirmed three gaps in the Cloudflare Access SSO paths added in #1058 — the CF cohort had strictly weaker token-theft protection than password users: 1. `GET /cf-access-logout` (`cfAccessRedirectLogin.ts:230`) was synchronous and only called `clearRefreshTokenCookie` — no `revokeAllUserTokens`, no jti revocation. After "Sign out", an exfiltrated access token stayed valid to expiry and the 7-day refresh token remained fully usable. `Header.tsx` routes CF logout exclusively through this endpoint and skips `apiLogout()`. 2. Both CF token-mint paths (`cfAccessLogin.ts:164`, `cfAccessRedirectLogin.ts:160`) called `createTokenPair` with no `refreshFam`, so CF-minted tokens fall into the `/refresh` handler's legacy skip path and the family-revocation reuse-detection (RFC 9700 §4.13.2) never fires for them — exactly the invariant `refreshTokenFamily.ts`'s docstring warns about. 3. The logout redirect origin was built from the attacker-controllable `Host` header (open redirect off the Breeze origin). **Fix:** - Logout resolves the refresh cookie, verifies the JWT (signature-checked, so a forged cookie can't trigger revocation for another user), then `revokeAllUserTokens(sub)` + `revokeRefreshTokenJti(jti)` — mirroring `POST /logout`. Missing/invalid cookie or Redis failure still clears + 302s (no 500). - Both mint paths now do `mintRefreshTokenFamily` → `createTokenPair(..., { refreshFam })` → `bindRefreshJtiToFamily`, same as `/login`. - Redirect origin comes from `DASHBOARD_URL || PUBLIC_APP_URL` (the established pattern in `login.ts`/`password.ts`); https-pinned `Host` fallback only when neither is set. - Docs now instruct covering `/api/v1/auth/cf-access-login` + `/cf-access-logout` in the Access application and warn against an `/api/*` bypass swallowing them. **Tests:** 11 new tests (logout revocation incl. no-cookie/invalid-cookie/Redis-failure paths, family binding on both mint paths incl. the MFA temp-token short-circuit, spoofed-Host redirect cases). `cfAccessRedirectLogin.test.ts` + `cfAccessLogin.test.ts`: 32/32 pass; `login.test.ts` + `helpers.test.ts` regression: 11/11 pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Optional support for self-hosters who put Breeze behind Cloudflare Access (Zero Trust). Today such a deployment forces a double login: Cloudflare Access auth, then the Breeze login form. This adds two opt-in, off-by-default capabilities so the CF Access identity can satisfy Breeze:
/auth/login— when enabled, a request carrying a validCf-Access-Jwt-Assertion(verified against the team's JWKS, with AUD + issuer checks) logs the matching Breeze user in without re-prompting.bootstrapFromCfAccessRedirect) that mints a Breeze session from the CF Access identity and a logout chain that breaks the SSO redirect loop on sign-out.Addresses Discussion #702.
Design / safety
CF_ACCESS_TRUST_ENABLED=trueand bothCF_ACCESS_TEAM_DOMAINandCF_ACCESS_AUDset; boot validation enforces this.https://<team-domain>/cdn-cgi/access/certs(no SSRF surface), checks signature,aud, and issuer. An attacker-issued token from a different team domain is rejected (covered by tests).CF_ACCESS_TRUSTS_MFAis a separate explicit opt-in: it marks the resulting session MFA-satisfied by operator assertion (i.e. the operator is asserting CF Access enforces MFA upstream). Default off.Files
services/cfAccessJwt.ts,middleware/cfAccessLogin.ts,routes/auth/cfAccessRedirectLogin.ts(+ tests).routes/auth/login.ts,routes/auth/index.ts,routes/config.ts,config/env.ts,config/validate.ts.stores/auth.ts(bootstrapFromCfAccessRedirect),stores/featuresStore.ts,components/auth/LoginPage.tsx,components/auth/AuthOverlay.tsx.deploy/cloudflare-access-trust.mdx.Tests
tscclean; full API vitest 5827 passed / 0 failed (32 new tests across the 3 CF files cover happy path, wrong issuer, wrong aud, missing config, MFA assertion).astro checkclean; web vitest green.