Keycloak cookie SSR auth + admin role gate (hooks.server.ts) #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type
Feature
Lineage
Parent / coordinator. Decomposed 2026-05-03 per scope review (review-1132-2026-05-03 verdict: NEEDS_REFINEMENT — over the 5-minute rule, recommend split). Hard depends on
forgejo_admin/westside-admin#6(scaffolding, closed) andforgejo_admin/pal-e-platform#301(Keycloak clientwestside-admincreated in realm — done 2026-05-03 viasop-keycloak-client-creation).Repo
forgejo_admin/westside-adminUser Story
story-westside-admin-admin-row-crud. Implements the Keycloak admin gate safety constraint perarch-deployment-westside-adminandarch-dataflow-westside-adminFlow 1.Context
westside-admin sits behind a public Tailscale funnel. Per
feedback_funnel_requires_auth(driving rule from the 2026-04-10 PII leak retro), every funnel-exposed app must have airtight auth documented before merge. Cookie SSR model: HttpOnly+Secure cookie holds AES-GCM ciphertext of tokens, JWT validated server-side per request via Keycloak's JWKS endpoint, no Bearer token in browser, no XSS exfiltration path.The Keycloak
westside-adminconfidential OIDC client was created in thewestside-basketballrealm on 2026-05-03 viasop-keycloak-client-creation(admin-console-driven). The client_secret is landing viaforgejo_admin/pal-e-deployments#147(QA-approved, awaiting merge). All 5 env vars (KEYCLOAK_URL,KEYCLOAK_REALM,KEYCLOAK_CLIENT_ID,KEYCLOAK_CLIENT_SECRET,COOKIE_SIGNING_KEY) will be present in the cluster Secret once #147 merges.No internal prior art — westside-contracts has zero Keycloak code (uses signed-token URLs); westside-app uses browser-side
keycloak-js(adapter-static). This decomposition is the new pattern reference for adapter-node SSR-Keycloak consumers.Decomposition (consolidated spec)
This parent ticket is non-executable — its work is delivered through 4 sub-tickets that each live within the 5-minute rule:
#14keycloak.tslib (JWKS cache, JWT verify, AES-GCM, OIDC state, token refresh) + unit tests#15hooks.server.tshandle hook +app.d.tsLocals extension#16/auth/login,/auth/callback,/auth/logoutendpoints#17(unauthorized)route group 403 pageSub-tickets carry their own File Targets, Acceptance Criteria, Test Expectations, and Constraints. The list below is the rollup of acceptance criteria across all 4 sub-tickets — this parent closes only when every sub-ticket's PR is merged AND the integration ACs are verified end-to-end.
File Targets (rollup — see sub-tickets for individual scopes)
Create across the 4 sub-tickets:
src/lib/server/keycloak.ts(#14)src/lib/server/keycloak.test.ts(#14)src/hooks.server.ts(#15)src/app.d.tsextension (#15)src/routes/auth/login/+server.ts(#16)src/routes/auth/callback/+server.ts(#16)src/routes/auth/logout/+server.ts(#16)src/routes/(unauthorized)/+page.svelte(#17)src/routes/(unauthorized)/+layout.svelte(#17)Update:
package.json— addjose(#14)Acceptance Criteria (integration rollup)
/302s to Keycloak/authorizewith PKCE + a freshstateparameter (signed with COOKIE_SIGNING_KEY, stored in transient HttpOnly cookie).statematches transient cookie BEFORE code exchange — mismatched/missing state returns 400, never proceeds.adminrole passes through to the page.adminrole sees the 403 page with logout button (NOT redirected away).westside-admin, exp not passed, iss =${KEYCLOAK_URL}/realms/westside-basketball.Test Expectations
Constraints (rollup)
westside_admin_session.COOKIE_SIGNING_KEYenv var.https://westside-admin.tail5b443a.ts.net/auth/callback(exact match — no/*).stateparameter is non-negotiable — funnel-exposed apps without state validation are vulnerable to CSRF on the callback.Checklist
feedback_review_before_dispatch)feedback_funnel_requires_authRelated
project-westside-admin— project this work is forarch-dataflow-westside-admin— Flow 1 (the auth sequence this implements)arch-deployment-westside-admin— deployment sidefeedback_funnel_requires_auth— driving safety constraintsop-keycloak-client-creation— Keycloak side (done)forgejo_admin/pal-e-platform#301— SOP authoring ticketforgejo_admin/pal-e-deployments#147— env-var landing PR (must merge before integration ACs run)forgejo_admin/westside-admin#14— sub-task 1 (keycloak.ts lib)forgejo_admin/westside-admin#15— sub-task 2 (hooks.server.ts)forgejo_admin/westside-admin#16— sub-task 3 (auth endpoints)forgejo_admin/westside-admin#17— sub-task 4 (403 page)review-1132-2026-05-03— scope review that drove the decompositionScope Review: NEEDS_REFINEMENT
Review note:
review-1090-2026-04-25Strong template, all sections present, file targets correct for SvelteKit, dependencies map cleanly to scaffolding (#6) and pal-e-platform Keycloak client (#301). Auth model (HttpOnly cookie SSR + AES-encrypted cookie + JWKS validation) is the right call for a funnel-exposed admin app per
feedback_funnel_requires_auth.Issues to fix before todo
project-westside-admin,story-westside-admin-admin-row-crud,arch-hooks-server,arch-dataflow-westside-admin,arch-deployment-westside-admindo not exist in pal-e-docs. The body referencesarch-dataflow-westside-adminFlow 1 as the source of truth diagram — this is a hard blocker for the dev agent.[SCOPE]KEYCLOAK_CLIENT_SECRETis needed here. Resolve #301 first.[SCOPE]Body refinements
stateparameter generation + validation (CSRF defense — currently absent).KEYCLOAK_URL,KEYCLOAK_REALM,KEYCLOAK_CLIENT_ID,KEYCLOAK_CLIENT_SECRET(if confidential),COOKIE_SIGNING_KEY, plus JWKS URL construction note.Decomposition
7 files + 7 ACs + AES cookie crypto + JWT verify + OIDC state CSRF exceeds the 5-minute rule. Recommend route to
skill-decompose-ticketwith this split:src/lib/server/keycloak.ts+src/app.d.ts(pure library, unit-testable)src/hooks.server.ts(handle hook + role gate)src/routes/auth/callback/+server.ts+auth/logout/+server.ts(OIDC flow + state validation)src/routes/(unauthorized)/+page.svelte(UI only)[DECOMPOSE]recommended; tight coupling means accepting as-oversized with a second-pass budget is also defensible — Ava's call.Prior art
westside-app uses browser-side keycloak-js (do NOT copy). westside-contracts is adapter-node — dev agent should grep it for any existing SSR auth helper before writing
keycloak.tsfrom scratch.Scope Review: READY (re-review)
Review note:
review-1090-2026-04-25-v2Previous:
review-1090-2026-04-25(NEEDS_REFINEMENT)All v1 recommendations resolved:
Residual minor: no standalone
arch-hooks-servernote, butarch-dataflow-westside-adminFlow 1 is the de-facto source of truth for the hook flow. Not a blocker.Ticket clears the backlog -> todo review gate. Do not advance to next_up until hard dependencies clear: #6 (scaffolding) merged + pal-e-platform#301 (Keycloak client + public-vs-confidential decision) finalized.
build/index.htmldoesn't exist for adapter-node #12Scope Review: NEEDS_REFINEMENT
Review note:
review-1132-2026-05-03Scope is technically sound and the issue body is high quality, but it's over the 5-minute rule by its own admission, has two label/arch-note gaps, and the "prior art" hint is misleading.
Issues:
[LABEL]Board item #1132 carriesstory:superuser-onboard-service; project Safety Constraints requiresstory:admin-row-crud(mirrored item on board-westside-admin already uses this). Swap the label.[SCOPE]arch:keycloaklabel has no backing note in pal-e-docs. Same gap exists on board item #1096 (pal-e-platform#301). Decide create-now vs defer.[BODY]"Prior art: westside-contracts may have SSR auth helpers" is misleading — westside-contracts has zero Keycloak code (signed-token URLs only); westside-app uses browser-side keycloak-js (adapter-static). Implement from scratch usingjose.[BODY]Optional: add a JWKS-unreachable resilience AC.[DECOMPOSE]6 new files + 10 AC + crypto + OIDC state + funnel-auth critical path. Over the 5-min rule. The 4-way split proposed in the issue body (keycloak.ts lib / hooks.server.ts+app.d.ts / auth callback+logout / 403 page) is sound. Ava's call: accept oversized with dev agent on standby, or route toskill-decompose-ticket.Sequencing note: Do not move to
in_progressuntilpal-e-deployments#147is merged and ArgoCD has synced the secret — otherwise integration / SSO ACs cannot be validated.Verified ground truth:
app.d.tsandpackage.jsonexist as expected; adapter is@sveltejs/adapter-node;src/lib/server/exists and is empty.overlays/westside-admin/prod/westside-admin-secrets.enc.yamlon the open PR #147 branch (KEYCLOAK_URL, KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET, COOKIE_SIGNING_KEY).story-westside-admin-admin-row-crudexists; arch notesarch-dataflow-westside-admin(Flow 1 referenced) andarch-deployment-westside-adminexist;arch-keycloakdoes not exist.Closing — all 4 decomposed sub-tasks shipped:
Functional integration end-to-end (admin login → cookie → admin page; non-admin login → 403 page) becomes testable once ArgoCD finishes reconciling the latest image. End-to-end manual validation is the next gate; until then, board items stay in
validationcolumn. Two follow-ups remain in backlog: #19 (CI vitest gate) and #22 (refresh_exp lib extension).