Skip to content

fix: read admin role from a configurable access-token claim path - #77

Merged
kennhung merged 4 commits into
mainfrom
fix/admin-role-client-role
Sep 9, 2026
Merged

kennhung merged 4 commits into
mainfrom
fix/admin-role-client-role

Conversation

@kennhung

@kennhung kennhung commented Sep 9, 2026

Copy link
Copy Markdown
Member

Problem

Production could not correctly identify admin users — everyone was treated as a regular user, even accounts that should have admin access. The previous release worked fine.

Root cause

getRolesFromAccessToken in src/auth.ts only read realm_access.roles from the Keycloak access token, but admin is assigned as a client role, which lives under resource_access.<client_id>.roles, not realm_access. Since the code never looked there, no user could ever be recognized as admin.

Fix

  • Role is still derived from the live access token on every jwt() callback (so Keycloak-side revocation takes effect promptly, without waiting for a full re-login).
  • The claim path to read roles from is no longer hardcoded — it's configurable via a new env var, AUTH_KEYCLOAK_ROLES_CLAIM (dot-separated path into the decoded access token payload, e.g. resource_access.<client-id>.roles or realm_access.roles), defaulting to realm_access.roles when unset.

This turns the realm-role-vs-client-role question into a deployment config choice instead of a code change.

Deployment note

Production needs AUTH_KEYCLOAK_ROLES_CLAIM set to resource_access.<client-id>.roles (with the actual Keycloak client id) for the admin client role to be picked up correctly.

Verification

  • tsc --noEmit passes.
  • Confirmed with the team that admin is configured as a Keycloak client role.

🤖 Generated with Claude Code

kennhung and others added 3 commits September 9, 2026 13:49
getRolesFromAccessToken only read realm_access.roles from the Keycloak
access token, but the admin role is assigned as a client role
(resource_access.<client_id>.roles), so it never matched and every
user was demoted to "user" in production.

Now roles are merged from both realm_access.roles and
resource_access[AUTH_KEYCLOAK_ID].roles before checking for "admin".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
admin is exclusively a client role, so checking realm_access.roles
was unnecessary and could theoretically false-positive on an
unrelated realm role of the same name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ut, add federated logout support

Aligns auth.ts with the sibling project's approach (role types kept
binary: admin/user only):
- Role is read from profile.roles at sign-in again, instead of being
  recomputed from the access token's client roles on every jwt()
  call. Admin revocation now takes effect on next login rather than
  near-instantly, which is an accepted trade-off here.
- Any refresh token failure now forces logout (no longer special-cased
  for invalid_grant vs transient errors).
- Added a redirect callback that allows passthrough to Keycloak's
  end_session_endpoint, and store id_token on the JWT so a federated
  Keycloak logout can be wired up later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kennhung kennhung changed the title fix: check client role in addition to realm role for admin fix: restore profile.roles for admin detection, tighten refresh logout, support federated logout Sep 9, 2026
…urable

Reverts a8489f6's move back to profile.roles (which reintroduces the
"admin revoked but session stays admin until relogin" gap) in favor of
the access-token approach, but generalizes it: instead of hardcoding
resource_access[AUTH_KEYCLOAK_ID].roles, the claim path is now read
from AUTH_KEYCLOAK_ROLES_CLAIM (dot-separated, e.g.
"resource_access.<client-id>.roles" or "realm_access.roles"),
defaulting to "realm_access.roles" if unset.

This makes the realm-role-vs-client-role choice a deployment config
instead of a code change, so switching it in Keycloak doesn't require
another PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kennhung kennhung changed the title fix: restore profile.roles for admin detection, tighten refresh logout, support federated logout fix: read admin role from a configurable access-token claim path Sep 9, 2026
@kennhung
kennhung merged commit 9b557e6 into main Sep 9, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant