[#50] Setup keycloak - #162
Conversation
|
@omargoher Remember the authors file, and license header |
|
@omargoher We will keep this as draft until everything is in place. Then take it out of draft mode, and PTAL me |
8d9c649 to
4434cbe
Compare
Migrate cookie-based auth to Keycloak OIDCReplace the custom cookie session authentication system with Keycloak What changedReplace custom cookie session authentication with Keycloak OIDC bearer Changes:
Backend resources
Infrastructure
Breaking changeCookie-based sessions no longer work. All clients must send: React frontend is not yet updated and will not work until the frontend How to testNote: should Apply how to start section in first msg Two helper scripts are provided under 1. Create a Keycloak user ./keycloak/create-user.sh <username> <email> <password> <role> <firstName> <lastName>
# examples
./keycloak/create-user.sh admin admin@mnemosyne-systems.ai admin admin System Administrator
./keycloak/create-user.sh user1 user1@mnemosyne-systems.ai user1 user John Doe
./keycloak/create-user.sh support1 support1@mnemosyne-systems.ai support1 support Sarah Johnson2. Get a JWT token ./keycloak/login.sh <username> <password>
# example
./keycloak/login.sh admin admin3. Call any endpoint TOKEN=$(./keycloak/login.sh user1 user1)
curl -s "http://localhost:8080/api/user/tickets" \
-H "Authorization: Bearer $TOKEN" | jq .Role → endpoint mapping
|
|
@omargoher Please, rebase and resolve conflicts such that we can review |
4434cbe to
f2211f4
Compare
|
@jesperpedersen Conflicts resolved and branch rebased successfully. |
|
@omargoher See CI |
|
@jesperpedersen Sorry, I forgot to run the tests before pushing. Some resource tests are currently failing. I will fix tests and push my changes. |
f2211f4 to
e98e684
Compare
|
@jesperpedersen Tests are now fixed. |
|
@omargoher We need to move the keycloak stuff to |
I want to know more details about why we should do this. Using parameters in the URL follow REST best practices ! |
|
@omargoher Parameters in the URL leaks the data model to the user. It also make it more difficult to make precise bookmarks. So, REST URLs is a no-go |
|
@omargoher REST URLs are for API calls, not UI |
|
@omargoher Although REST is basically dead - we use HTTP w/ JSON payload instead |
Good point. I noticed that our backend is still handling some redirects and UI-related navigation. Would it make sense to move those responsibilities to React and keep the backend focused on exposing APIs only? That way, the frontend can manage the UI flow and control what appears in the URL, while authentication and session handling stay managed through keycloak-js and the Keycloak server. |
|
@omargoher Yes, backend is an API and its data model isn't exposed |
|
Hi @omargoher excellent work man:) These @get redirect paths (like |
|
Hi @omargoher any update ? |
|
@sksingh2005 Sorry, I'm busy with some work until June 12. Also, frontend isn't my strongest area. If you want to pick up that part, go ahead. You can use this repo as a reference: |
|
@omargoher How are you doing on this ? |
|
Hi @jesperpedersen , I've been busy lately, but I'll pick this up and keep you posted on the progress. |
|
@jesperpedersen PTAL |
|
@omargoher Excellent ! |
|
@omargoher This a massive patch |
|
@omargoher Remember to add yourself to 97-acknowledgement.md |
|
@omargoher Update to Keycloak 26.7.0 |
d15910e to
005d625
Compare
Done
Done and
|
OverallNo issues found Code
Security
MemoryNo issues found Performance
Test Suite
Documentation
Conclusionorangu approves this patch Generated by: orangu 1.2.0 (unsloth/gemma-4-E4B-it-GGUF) |
|
I'm starting with this |
|
@omargoher Lets start with the spelling mistake |
|
@omargoher You can use orangu locally to test your patch - |
|
@omargoher Where are we on this ? |
005d625 to
10e7092
Compare
|
@jesperpedersen, sorry for the delay. |
|
@sksingh2005 PTAL |
|
@omargoher CI |
… (backend + frontend + tests + docs)
10e7092 to
0c36391
Compare
@jesperpedersen Done |
There was a problem hiding this comment.
Pull request overview
This PR begins the migration from the legacy cookie-based auth to Keycloak-backed OIDC across the stack, adding local Keycloak infrastructure, frontend Keycloak login/session handling, and backend Quarkus OIDC enforcement with just-in-time (JIT) user provisioning.
Changes:
- Add Keycloak dev infrastructure (compose service + realm templating/generation + helper scripts/docs).
- Frontend: introduce a Keycloak
AuthProvider, replace login/logout/password-change flows with Keycloak redirects, and inject Bearer tokens into/api/*calls. - Backend: enable Quarkus OIDC + role-based access control and replace cookie-based user resolution with
CurrentUser+UserProvisioningService; update tests accordingly.
Reviewed changes
Copilot reviewed 80 out of 84 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/frontend/src/routes/CoreRoutes.tsx | Stop passing sessionState into PasswordPage and keep route protected. |
| src/frontend/src/pages/PasswordPage.tsx | Replace in-app password change form with Keycloak UPDATE_PASSWORD redirect flow. |
| src/frontend/src/pages/LoginPage.tsx | Trigger Keycloak login when unauthenticated; add loading/redirect UI. |
| src/frontend/src/main.tsx | Wrap the app in AuthProvider to initialize Keycloak and auth state. |
| src/frontend/src/components/layout/SessionInactivityManager.tsx | Replace cookie logout with Keycloak logout on inactivity timeout. |
| src/frontend/src/components/layout/AuthenticatedHeader.tsx | Replace /logout link with Keycloak logout action. |
| src/frontend/src/auth/useAuth.ts | Add useAuth hook for accessing auth context. |
| src/frontend/src/auth/keycloak.ts | Add Keycloak client initialization using Vite env vars. |
| src/frontend/src/auth/AuthProvider.tsx | Add Keycloak init + token refresh loop + global fetch interceptor. |
| src/frontend/src/auth/AuthContext.ts | Add AuthContext to expose keycloak/authenticated/initialized. |
| src/frontend/package.json | Add keycloak-js dependency. |
| src/frontend/package-lock.json | Lockfile updates for keycloak-js and dependency graph. |
| src/backend/test/resources/application.properties | Disable OIDC in tests and enable Quarkus test security with authenticated path rules. |
| src/backend/test/java/ai/mnemosyne_systems/resource/TicketImportResourceTest.java | Update tests to use @TestSecurity/@JwtSecurity instead of cookie login. |
| src/backend/test/java/ai/mnemosyne_systems/resource/TAMAccessTest.java | Update access tests to JWT-based test security; adjust expectations (e.g., 403 vs redirects). |
| src/backend/test/java/ai/mnemosyne_systems/resource/SuperuserAccessTest.java | Convert cookie-based access tests to JWT-based test security. |
| src/backend/test/java/ai/mnemosyne_systems/resource/PasswordResetResourceTest.java | Remove assertions tied to legacy password hashing behavior. |
| src/backend/test/java/ai/mnemosyne_systems/resource/AccessTestSupport.java | Remove password creation + cookie login helpers; adjust helper signatures. |
| src/backend/main/resources/application.properties | Add Quarkus OIDC config + auth permissions; set logging categories for security/OIDC. |
| src/backend/main/java/ai/mnemosyne_systems/util/CurrentUser.java | Add request-scoped current user resolver supporting JWT and test identities. |
| src/backend/main/java/ai/mnemosyne_systems/service/UserProvisioningService.java | Add JIT provisioning and role→type mapping from Keycloak JWT claims. |
| src/backend/main/java/ai/mnemosyne_systems/resource/UserViewApiResource.java | Convert cookie auth to @RolesAllowed + CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/UserTicketApiResource.java | Convert cookie auth to @RolesAllowed + CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/UserResource.java | Convert many user flows to CurrentUser-based access checks and remove cookie params. |
| src/backend/main/java/ai/mnemosyne_systems/resource/TimezoneResource.java | Enforce roles and use CurrentUser instead of cookie auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/TicketWorkbenchApiResource.java | Enforce support role and use CurrentUser instead of cookie auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/TicketResource.java | Add role annotations and route ticket views via CurrentUser without cookies. |
| src/backend/main/java/ai/mnemosyne_systems/resource/TicketRatingApiResource.java | Enforce user/superuser roles and use CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/TicketImportApiResource.java | Enforce admin/support roles and use CurrentUser for actor resolution. |
| src/backend/main/java/ai/mnemosyne_systems/resource/TamUserApiResource.java | Enforce tam role and use CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/SupportUserApiResource.java | Enforce support role and use CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/SupportTicketApiResource.java | Enforce support role and use CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/SupportResource.java | Enforce support role and remove cookie-based checks from HTML routes. |
| src/backend/main/java/ai/mnemosyne_systems/resource/SuperuserTicketApiResource.java | Enforce superuser role and use CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/SuperuserResource.java | Enforce superuser role and remove cookie-based checks from HTML routes. |
| src/backend/main/java/ai/mnemosyne_systems/resource/SuperuserDirectoryApiResource.java | Enforce superuser role and use CurrentUser. |
| src/backend/main/java/ai/mnemosyne_systems/resource/RssResource.java | Enforce per-feed roles and use CurrentUser rather than cookie auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/ReportResource.java | Enforce reporter roles and use CurrentUser for export flows. |
| src/backend/main/java/ai/mnemosyne_systems/resource/ReportApiResource.java | Enforce reporter roles and use CurrentUser to build report data. |
| src/backend/main/java/ai/mnemosyne_systems/resource/ProfileResource.java | Enforce roles and remove cookie-based auth for profile HTML routes. |
| src/backend/main/java/ai/mnemosyne_systems/resource/ProfileApiResource.java | Enforce roles and remove cookie-based auth for profile APIs. |
| src/backend/main/java/ai/mnemosyne_systems/resource/OwnerResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/OwnerApiResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/LogoutResource.java | Mark legacy logout flow as obsolete (still present). |
| src/backend/main/java/ai/mnemosyne_systems/resource/LevelResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/LevelApiResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/HomeResource.java | Enforce authenticated access and use CurrentUser to route home. |
| src/backend/main/java/ai/mnemosyne_systems/resource/EntitlementResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/EntitlementApiResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/CompanyResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/CompanyApiResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/CategoryResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/CategoryApiResource.java | Enforce admin role and remove cookie-based auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/AuthResource.java | Mark legacy auth flow as obsolete (still present). |
| src/backend/main/java/ai/mnemosyne_systems/resource/AttachmentResource.java | Enforce roles and use CurrentUser instead of cookie auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/AttachmentApiResource.java | Enforce roles and use CurrentUser instead of cookie auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/ArticleResource.java | Enforce roles, use CurrentUser, and adjust edit/admin checks. |
| src/backend/main/java/ai/mnemosyne_systems/resource/ArticleApiResource.java | Enforce roles and use CurrentUser instead of cookie auth. |
| src/backend/main/java/ai/mnemosyne_systems/resource/AppSessionResource.java | Make session endpoint @PermitAll and resolve user via CurrentUser.getOrNull(). |
| src/backend/main/java/ai/mnemosyne_systems/resource/AdminUserApiResource.java | Enforce admin role via annotation instead of cookie-based gate. |
| src/backend/main/java/ai/mnemosyne_systems/model/User.java | Add keycloakId and allow nullable password hash to support Keycloak-managed users. |
| README.md | Document Keycloak SSO and link to Keycloak integration guide. |
| pom.xml | Add quarkus-oidc and quarkus-test-security-jwt dependencies. |
| docker-compose.yml | Add Keycloak service and realm import volume for local dev. |
| doc/manual/en/97-acknowledgement.md | Add contributor name. |
| doc/manual/en/25-password-reset.md | Update password reset docs to Keycloak-managed flow. |
| doc/manual/en/22-security.md | Rewrite security docs for Keycloak SSO + JIT provisioning + token renewal. |
| doc/DEVELOPERS.md | Add Keycloak OIDC authentication section and link to guide. |
| doc/BUILDING.md | Document Keycloak service in compose/platform target. |
| contrib/keycloak/README.md | Add Keycloak integration guide (flow, config, scripts, role mapping). |
| contrib/keycloak/login.sh | Add helper script for obtaining JWT token via password grant. |
| contrib/keycloak/keycloak-realm.template.json | Add templated realm definition to generate project-specific realm JSON. |
| contrib/keycloak/generate_realm.py | Add script to generate realm JSON from .env-keycloak. |
| contrib/keycloak/create-user.sh | Add helper script to create a Keycloak user and assign realm role. |
| contrib/keycloak/.env-keycloak.example | Add example Keycloak environment config for realm generation. |
| AUTHORS | Add contributor name. |
| .gitignore | Ignore Keycloak env file and generated realm file. |
Files not reviewed (1)
- src/frontend/package-lock.json: Generated file
Suppressed comments (1)
src/backend/main/java/ai/mnemosyne_systems/service/UserProvisioningService.java:58
- New-user provisioning persists
emailandnamedirectly from JWT claims, but those claims can be missing depending on client scopes/mappers. Since both columns are non-null, this should validate and/or provide a safe fallback forpreferred_usernamebefore persisting.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // Only attach Authorization header to local API endpoints (/api/*) | ||
| const isLocalApi = | ||
| url.startsWith("/api/") || | ||
| url.includes(window.location.origin + "/api/"); | ||
|
|
| user.keycloakId = keycloakId; // stamp keycloakId if missing | ||
| user.email = email; | ||
| user.name = username; | ||
| user.fullName = fullName; | ||
| user.type = resolveType(roles); |
| jakarta.json.JsonObject realmAccess = jwt.getClaim("realm_access"); | ||
| List<String> roles = realmAccess != null | ||
| ? realmAccess.getJsonArray("roles").getValuesAs(JsonString.class).stream().map(JsonString::getString) | ||
| .toList() | ||
| : List.of(); |
| "verifyEmail": "${KEYCLOAK_ENABLE_VERIFY_EMAIL}", | ||
| "registrationAllowed": "${KEYCLOAK_ENABLE_REGISTRATION}", | ||
| "resetPasswordAllowed": "${KEYCLOAK_ENABLE_RESET_PASSWORD}", |
| quarkus.log.category."io.quarkus.oidc".level=DEBUG | ||
| quarkus.log.category."io.quarkus.security".level=DEBUG |
| public User get() { | ||
| if (!loaded) { | ||
| resolved = load(); | ||
| loaded = true; | ||
| } | ||
| return resolved; | ||
| } |
|
@omargoher Thanks for the update. |
Summary
This is the first phase of the Keycloak migration.
The PR introduces Keycloak infrastructure, realm generation, and backend OIDC configuration.
i added Keycloak service in
docker-compose.yml.and to load Keycloak configuration: created
keycloak-realm.template.json,.env-keycloak.exampleand
generate_realm.pyscript to generate a project-specifickeycloak-realm.jsonthis avoids changing
keycloak-realm.jsonmanually and allows configuration changes through.env-keycloak.i also added OIDC configuration in
src/backend/main/resources/application.propertiesand made
/api/*endpoints private.all requests to
/api/*now require a valid JWT token in theAuthorizationheader, so the current cookie-based auth no longer works for protected APIs.How to start
.env-keycloak.exampleadd your configuration to
.env-keycloakgenerate the realm file