Skip to content

S11 PLAN

Move production browser authentication to BFF cookies while Auth0 remains the identity provider.

Purpose: This isolates cookie/session/CSRF/Redis risk from OpenIddict and migration risk. Output: Pre-change baseline, Auth0 BFF client/config, ready revert, and exact 24-hour evidence.

@docs/planning/auth0-to-openiddict/phases/05-cutover-decommission/slices/S10/S10-SUMMARY.md @scripts/auth-migration/query-auth-telemetry.sh @scripts/auth-migration/live-smoke.sh

Task 1: Capture the baseline and authorise the confidential Auth0 secret transfer None (Auth0/GCP state and redacted baseline evidence) Before editing GitOps, run the checked-in direct-Auth0/API query mode for an explicit completed 24-hour UTC interval and store query text/hash and the Auth0 log-derived login attempt/success aggregates in redacted JSON. This pre-flip baseline covers those Auth0 login aggregates only: the syrf_bff_auth_* instruments do not exist before the BFF is enabled, so there is no pre-flip BFF 5xx rate or callback p95 to capture and neither is part of this baseline — Task 3's own 24-hour BFF-on-Auth0 window is the authoritative source for both. Verify the confidential Auth0 BFF client uses the canonical public callback /api/auth/callback and logout origin and leaves the SPA client/connections/callbacks intact. The app origin must be one of that client's Allowed Logout URLs: BFF logout sends the browser to Auth0's /oidc/logout with client_id and id_token_hint, because the shared tenant does not advertise an end-session endpoint and its discovery setting stays off (BFF how-to). In the restricted worksheet, authorise the exact logical destination projects/&lt;production-project&gt;/secrets/syrf-auth0-bff-production/versions/&lt;approved-version&gt;, map the Auth0 confidential application's client secret as its approved source, transfer it through the approved secret channel, and require exactly that version enabled. Never place the value in Terraform/GitOps, argv, logs, diffs or public evidence. Dry-run both cutover and rollback threshold query modes against fixtures/live data. SYRF_WORKTREE="\({SYRF_WORKTREE:?}" && "\)SYRF_WORKTREE/scripts/auth-migration/query-auth-telemetry.sh" --mode direct-auth0-baseline --provider auth0 --environment production --start "\({BASELINE_START_UTC:?}" --end "\)" --output /tmp/m005-s11-baseline.json && exec 3<"\({AUTH0_BFF_SECRET_MANIFEST:?restricted manifest path}" && "\)SYRF_WORKTREE/scripts/auth-migration/preflight.sh" --mode production-auth0-bff-secret-version --environment production --secret-manifest-fd 3 --require-approved-transfer --evidence /tmp/m005-s11-secret-counts.json && "$SYRF_WORKTREE/scripts/auth-migration/check-redacted-evidence.sh" /tmp/m005-s11-baseline.json /tmp/m005-s11-secret-counts.json Confirm the interval is exactly 24 hours, client type/URLs are exact, the restricted source/destination/version row is approved and enabled, and no existing Auth0 SPA setting was removed. Baseline/query evidence, the confidential BFF client, and its authorised enabled GCP version exist before the production diff.

Task 2: Switch Web to BFF backed by explicit Auth0 cluster-gitops/syrf/environments/production/api/values.yaml, cluster-gitops/syrf/environments/production/web/values.yaml, cluster-gitops/plugins/local/extra-secrets-production/values.yaml Add target Kubernetes Secret auth0-bff to extra-secrets-production with property clientSecret mapped to GCP key syrf-auth0-bff-production; commit only this reference, never its value or version identifier. Set Web authProvider=bff; set API BFF enabled with explicit provider=auth0, exact custom-domain authority, confidential client ref auth0-bff/clientSecret, Redis Secret ref, secure cookies and a new generation. Keep IdentityService.BaseUrl absent so Auth0IdentityService remains active; keep JWT fallback, existing Auth0 SPA/management config and secrets. Prepare an inverse PR restoring direct Web Auth0/BFF disabled with another fresh generation. Validate the active and inverse render plus the exact ExternalSecret target/remoteRef before merge. SYRF_WORKTREE="\({SYRF_WORKTREE:?}" && GITOPS_WORKTREE="\)" && "\(SYRF_WORKTREE/scripts/auth-migration/preflight.sh" --mode production-bff-auth0 --syrf-worktree "\)SYRF_WORKTREE" --gitops-worktree "$GITOPS_WORKTREE" --require-secret-target auth0-bff --require-remote-key syrf-auth0-bff-production --require-property clientSecret --evidence /tmp/m005-s11-switch.json Before G2 confirm provider is Auth0 despite custom domain, the ExternalSecret maps only the authorised record/property, all replicas render Redis, IdentityService remains absent, and rollback generation differs. The switch/revert are reviewed, isolated to BFF mechanics, and preserve OpenIddict dark launch.

Task 3: Hold BFF-via-Auth0 for an exact 24 hours None (live production evidence only) Merge during the approved window and run production smoke for password/Google, /me, refresh/logout, account/admin, protected API, SignalR and same session through each API replica. With --expected-provider auth0 the harness does not automate the admin row (Auth0 administrator groups live in the shared tenant) or the emailed reset row, and records both as not run with a reason; the operator attests the admin surface. Query an exact 24-hour interval after rollout with --self-baseline. This window is the phase's self-baselining gate: it enforces the absolute thresholds (≥99% login success, refresh failure ≤1%, no sensitive logs, no in-memory store) and records its own observed auth 5xx rate and callback p95 as the authoritative baseline that S13 and every later window compare against, because no syrf_bff_auth_* series existed before this rollout. Write that window evidence to a retained absolute path outside /tmp (BFF_BASELINE_EVIDENCE) and keep it for the whole phase — reboot or tmpfiles cleanup of /tmp would make G4/G5 unreproducible. Otherwise merge the direct-Auth0 revert and prove stale BFF cookies route through disabled-selector/JWT behavior rather than old Redis sessions. SYRF_WORKTREE="\({SYRF_WORKTREE:?}" && "\)SYRF_WORKTREE/scripts/auth-migration/live-smoke.sh" --environment production --allow-production --expected-provider auth0 --expected-issuer https://signin.syrf.org.uk --require-two-replicas --evidence /tmp/m005-s11-smoke.json && "\(SYRF_WORKTREE/scripts/auth-migration/query-auth-telemetry.sh" --mode bff-window --provider auth0 --environment production --start "\)" --end "\({BFF_END_UTC:?}" --self-baseline --output "\)" && "\(SYRF_WORKTREE/scripts/auth-migration/check-redacted-evidence.sh" "\)" Approve G2 only after exactly 24 hours; on breach approve only completed direct-Auth0 rollback evidence. Production is stable on BFF/Auth0 for 24 hours or safely restored to direct Auth0.

Pre-created exact queries, authorised Auth0-to-GCP secret transfer, ExternalSecret mapping, switch/revert render, cross-replica smoke and exact 24-hour thresholds pass. M005-R12 passes without exposing OpenIddict to browser/admin production traffic.

After completion, create `slices/S11/S11-SUMMARY.md`.

Production Auth0 BFF client checklist (recorded 2026-10-07)

S09 rehearsed the BFF-on-Auth0 shape on staging with a new Auth0 application, "SyRF staging BFF" (camaradesuk/cluster-gitops#1530). Production needs the same, so Task 1's "confidential Auth0 BFF client" is this checklist. It was recorded with Chris alongside S09's option B rollback. Create nothing in production before S11 Task 1 is authorised.

New Auth0 Regular Web App "SyRF production BFF":

  • Application type: Regular Web App.
  • Allowed Callback URLs: https://syrf.org.uk/api/auth/callback.
  • Allowed Logout URLs: https://syrf.org.uk (the app origin only; Auth0 refuses a deeper path such as /auth/confirmation-email-sent with 400, so the Action email-sent pages return to the app root).
  • Application Login URI: https://syrf.org.uk/api/auth/login.
  • Grant types: Authorization Code and Refresh Token.
  • Refresh token rotation enabled.
  • Token endpoint authentication method: Client Secret (Post).
  • Connections enabled: Username-Password and google-oauth2.

GCP secret:

  • syrf-auth0-bff-production, property clientSecret, mapped by extra-secrets-production to the Kubernetes Secret auth0-bff, as Task 2 already requires. The value is transferred through the approved secret channel and never enters GitOps, argv, logs or evidence.

No change to existing Auth0 objects. The existing SPA application, the tenant's Default Audience (it stays empty), the Transform Token Action (it has no client allowlist, so it applies to the new application without an edit), and the tenant-wide RP-Initiated Logout End Session Endpoint Discovery setting (left untouched, because the tenant is shared) are all left as they are. BFF logout therefore uses {authority}/oidc/logout with client_id and id_token_hint, and the Action email-sent pages use {authority}/v2/logout, as described in the BFF how-to.

Staging counterpart: the "SyRF staging BFF" application may stay as a standing staging rollback path; that is Chris's decision after S27.