fix(sso): extract user roles from JWT access token for Keycloak compa…#20591
Merged
ishaan-jaff merged 1 commit intoBerriAI:mainfrom Feb 7, 2026
Merged
Conversation
…tibility Keycloak (and similar OIDC providers) include role claims in the JWT access token but not in the UserInfo endpoint response. Previously, roles were only extracted from UserInfo, causing all SSO users to default to internal_user_view_only regardless of their actual role. Changes: - Extract user roles from JWT access token in process_sso_jwt_access_token() when UserInfo doesn't provide them (tries role_mappings first, then GENERIC_USER_ROLE_ATTRIBUTE) - Handle list-type role values in get_litellm_user_role() since Keycloak returns roles as arrays (e.g. ["proxy_admin"] instead of "proxy_admin") - Add 9 new unit tests covering role extraction and list handling - Update 3 existing tests for new JWT decode behavior Closes BerriAI#20407
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Greptile OverviewGreptile SummaryFixes Keycloak SSO role mapping by extracting user roles from JWT access tokens. Keycloak includes role claims in JWT access tokens but not in UserInfo responses, causing all users to default to Key changes:
The implementation correctly prioritizes UserInfo roles over JWT roles, preventing unintended overrides. JWT decoding without signature verification is acceptable here since tokens are obtained through verified OAuth flows. Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/types.py | Added list handling to get_litellm_user_role() for Keycloak compatibility - takes first element from list inputs |
| litellm/proxy/management_endpoints/ui_sso.py | Extended process_sso_jwt_access_token() to extract user roles from JWT access tokens using role_mappings or GENERIC_USER_ROLE_ATTRIBUTE, only when UserInfo doesn't provide roles |
| tests/test_litellm/proxy/management_endpoints/test_ui_sso.py | Added 9 tests for list role handling and JWT role extraction, updated 3 existing tests to handle new JWT decoding behavior |
Sequence Diagram
sequenceDiagram
participant User
participant LiteLLM as LiteLLM Proxy
participant Keycloak as Keycloak IdP
participant UserInfo as UserInfo Endpoint
User->>LiteLLM: SSO Login Request
LiteLLM->>Keycloak: OAuth2 Authorization
Keycloak-->>LiteLLM: Authorization Code
LiteLLM->>Keycloak: Token Exchange
Keycloak-->>LiteLLM: Access Token (JWT with roles)
LiteLLM->>UserInfo: Request User Info
UserInfo-->>LiteLLM: User Data (no roles)
Note over LiteLLM: generic_response_convertor()<br/>extracts user data from UserInfo<br/>but user_role remains None
LiteLLM->>LiteLLM: process_sso_jwt_access_token()
Note over LiteLLM: Decode JWT Access Token<br/>(without signature verification)
alt Role Mappings Available
Note over LiteLLM: Extract groups from JWT<br/>determine_role_from_groups()
else Fallback to GENERIC_USER_ROLE_ATTRIBUTE
Note over LiteLLM: Extract role from JWT field<br/>get_litellm_user_role()
end
Note over LiteLLM: Handle list-type roles<br/>["proxy_admin"] → "proxy_admin"
LiteLLM->>LiteLLM: Set user_role if not already set
LiteLLM-->>User: Authenticated with correct role
This was referenced Feb 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relevant issues
Fixes #20407
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unitCI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🐛 Bug Fix
✅ Test
Changes
Problem
Keycloak (and similar OIDC providers) include role claims in the JWT access token but not in the UserInfo endpoint response. Previously,
process_sso_jwt_access_token()only extractedteam_idsfrom the JWT — roles were only read from the UserInfo response viageneric_response_convertor(). This caused all Keycloak SSO users to default tointernal_user_view_onlyregardless of their actual role assignment.Additionally,
get_litellm_user_role()expected a plain string but Keycloak returns roles as arrays (e.g.["proxy_admin"]), causing anAttributeErrorthat was silently caught, returningNone.Root causes
process_sso_jwt_access_token()decoded the JWT but only used it forteam_idsextraction. Role claims present in the token were ignored.get_litellm_user_role()called.lower()directly on the input. When Keycloak passes["proxy_admin"](a list), this raisedAttributeError→ caught → returnedNone→ user gotinternal_user_view_only.Fix
ui_sso.py—process_sso_jwt_access_token()now extracts user roles from the JWT access token when UserInfo doesn't provide them. It triesrole_mappingsfirst (group-based), then falls back toGENERIC_USER_ROLE_ATTRIBUTE. Only sets the role ifexisting_role is None(never overrides UserInfo).types.py—get_litellm_user_role()now handles list inputs by taking the first element before validation.test_ui_sso.py— Added 9 new unit tests for role extraction from JWT and list handling. Updated 3 existing tests to match new behavior.Reproduction
Reproduced end-to-end with a local Keycloak instance (realm with client roles + protocol mapper) and LiteLLM proxy. Before fix:
/sso/debug/loginshowed nouser_role. After fix: correctly showsproxy_admin/internal_userbased on Keycloak role assignment.Screenshots