Add Keycloak OIDC login with role-based route guards #9
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/westside-landing!9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "8-add-keycloak-oidc-login-flow-with-role-b"
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?
Summary
Integrates
@auth/sveltekitwith Keycloak as the OIDC provider to add authentication and role-based access control. The stats page (/) remains public, while/adminrequires theadminrole and/coachrequirescoachoradmin. Unauthenticated users are redirected to the Keycloak login page. A persistent login/logout UI component displays user name, roles, and sign-out functionality.Changes
src/auth.js(new) -- Auth.js configuration with Keycloak provider, JWT callback to extractrealm_accessroles, session callback to attach rolessrc/hooks.server.js(new) -- Re-exports the Auth.js handle for SvelteKit server hookssrc/routes/+layout.server.js(new) -- Injects the session into all pages via layout datasrc/routes/signin/+page.server.js(new) -- Form action for sign-in flowsrc/routes/signout/+page.server.js(new) -- Form action for sign-out flowsrc/lib/components/AuthStatus.svelte(new) -- Login/logout UI component showing user name, filtered roles (admin/coach/player), and sign-out buttonsrc/routes/+layout.svelte(modified) -- Integrates AuthStatus component into every pagesrc/routes/admin/+page.server.js(modified) -- Adds auth guard requiringadminrole, redirects unauthenticated userssrc/routes/coach/+page.server.js(modified) -- Adds auth guard requiringcoachoradminrole, redirects unauthenticated usersk8s/deployment.yaml(modified) -- AddsAUTH_SECRET,AUTH_KEYCLOAK_ID,AUTH_KEYCLOAK_SECRET(fromwestside-app-authSecret),AUTH_KEYCLOAK_ISSUER, andAUTH_TRUST_HOSTenv varspackage.json/package-lock.json(modified) -- Adds@auth/sveltekitv1.11.1 dependencyTest Plan
/(stats page) loads without authentication/adminredirects to Keycloak login when unauthenticated/coachredirects to Keycloak login when unauthenticatedadminrole -- verify/adminloadscoachrole -- verify/coachloads but/adminreturns 403/adminand/coachreturn 403westside-app-authKubernetes Secret withauth-secret,keycloak-client-id, andkeycloak-client-secretkeys before deployingnpm run buildpasses (verified)Review Checklist
npm run build)Related
westside-basketball, client:westside-appSelf-Review: PASS
Files reviewed: 12 changed (6 new, 6 modified)
Checklist
AUTH_SECRET,AUTH_KEYCLOAK_ID,AUTH_KEYCLOAK_SECRETsourced from K8s Secretwestside-app-authnpm run buildpassesnpm run check-- 0 new type errors (24 pre-existing from original codebase)@auth/sveltekitdocumentation+page.server.js(not+layout.server.js) per Auth.js best practicesatob()safe on Node 20 (Dockerfile base image)AUTH_TRUST_HOST=truerequired for Tailscale funnel reverse proxy$env/dynamic/private(no manualprocess.env)Closes #8in PR body for auto-closeDeployment prerequisite
Before deploying, create the K8s Secret:
No issues found. Ready for human review.
PR #9 Review
BLOCKERS
1. Admin form actions missing auth guards (security)
src/routes/admin/+page.server.js-- Theload()function correctly checks for authentication and theadminrole before rendering the page. However, theactionsobject (checkinandassign) has no auth checks at all. In SvelteKit,loadandactionsare separate entry points. An unauthenticated user (or a user without the admin role) can POST directly to/admin?/checkinor/admin?/assignto check in players or assign tryout numbers, bypassing the UI entirely.Each action needs the same auth guard pattern used in
load():The
request-only destructuring ({ request }) in each action must be changed to include the full event (or at minimumlocals) to accessauth().NITS
1.
atob()for JWT base64url decodingsrc/auth.jsline 17 --atob(parts[1])assumes standard base64, but JWT payloads use base64url encoding (characters-and_instead of+and/, no=padding). While the try/catch prevents crashes and Keycloak payloads rarely trigger the edge case, the correct approach is to replace-with+and_with/before callingatob(), or useBuffer.from(parts[1], 'base64url')which is available in Node.js.2. Svelte 4 slot syntax in a Svelte 5 codebase
src/lib/components/AuthStatus.sveltelines 26 and 32 --slot="submitButton"is legacy Svelte 4 syntax. This works today because@auth/sveltekitcomponents still use the old slot API, but it will break when the library migrates to Svelte 5 snippets. Not blocking since there is no fix available until the upstream library updates.SOP COMPLIANCE
8-add-keycloak-oidc-login-flow-with-role-breferences issue #8)Closes #8for auto-closewestside-app-auth)src/lib/server/api.jsis unmodified (basketball-api calls remain public, no auth added to API layer)/) remains public with no auth requirement$props(),$state(),$derived.by())BASKETBALL_API_URLand adds all required auth env varsAUTH_TRUST_HOST=truecorrectly set for reverse proxy / Tailscale funnelVERDICT: NOT APPROVED
One blocker: the admin form actions (
checkin,assign) are unprotected. This is a real security gap -- anyone who knows the endpoint can POST form data to check in players or assign tryout numbers without authentication. The fix is straightforward: add the same auth guard fromload()to each action handler, acceptingevent(or{ request, locals }) instead of just{ request }.