Skip to content

fix(sso): honor singular team_id_jwt_field in SSO callback - #27352

Merged
oss-pr-review-agent-shin[bot] merged 2 commits into
litellm_agent_oss_staging_05_07_2026from
litellm_fix-sso-team-id-singular-claim
May 7, 2026
Merged

fix(sso): honor singular team_id_jwt_field in SSO callback#27352
oss-pr-review-agent-shin[bot] merged 2 commits into
litellm_agent_oss_staging_05_07_2026from
litellm_fix-sso-team-id-singular-claim

Conversation

@ryan-crabbe-berri

@ryan-crabbe-berri ryan-crabbe-berri commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The SSO callback flow (generic_response_convertoradd_missing_team_member) was reading only the plural team_ids_jwt_field claim. IdPs that populate the singular team_id_jwt_field instead (Okta/Auth0 mapping a group → primary team) had their users created via SSO with teams: [] — never assigned to the team named in the JWT, regardless of how the user's user_metadata.team_id changed across logins.

This adds JWTHandler.get_all_jwt_team_ids() returning the deduplicated union of both claim sources, and threads it through the two branches that build all_teams in generic_response_convertor. The result: SSO logins now read the singular claim the same way the JWT-bearer path's find_and_validate_specific_team_id always has.

screenshots

Screenshot 2026-05-06 at 5 28 21 PM

Test plan

  • uv run pytest tests/test_litellm/proxy/auth/test_handle_jwt.py tests/test_litellm/proxy/management_endpoints/test_ui_sso.py — 240 pass.
  • New test: test_get_all_jwt_team_ids_unions_singular_and_plural covering all four input combinations (singular only, plural only, both no-overlap, both with overlap, neither).
  • End-to-end SSO repro against a real Auth0 tenant with the Phil-shaped post-login Action — confirmed user is now added to the team in user_metadata.team_id (BEFORE: teams: []; AFTER: teams: ["team-low"] after first login, dual membership after re-login with changed metadata).
  • Black formatting applied (uv run black on the three changed files).
  • Existing 183 SSO tests untouched and still pass — no regression in the SSO suite.

The SSO callback flow (`generic_response_convertor` → `add_missing_team_member`)
was reading only the plural `team_ids_jwt_field` claim. IdPs that populate the
singular `team_id_jwt_field` instead (Okta/Auth0 group → primary team) had their
users created via SSO with `teams: []` — never assigned to the team named in the
JWT, regardless of how the user_metadata changed across logins.

Adds `JWTHandler.get_all_jwt_team_ids()` returning the deduplicated union of
both claim sources, and threads it through both branches that build `all_teams`
in `generic_response_convertor`. JWT bearer auth path is intentionally
unchanged — pending customer feedback on whether membership reconciliation
there should be moved or stay additive.
@greptile-apps

greptile-apps Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the SSO callback flow so that users whose IdP sets only the singular team_id_jwt_field JWT claim (the Okta/Auth0 default) are correctly assigned to their team on login. Previously, generic_response_convertor read only the plural team_ids_jwt_field, leaving such users with teams: [].

  • Introduces JWTHandler.get_all_jwt_team_ids() that deduplicated-unions both claim shapes without falling back to team_id_default (which is a JWT-bearer auth-flow concern, not a token claim).
  • Replaces both get_team_ids_from_jwt call-sites in generic_response_convertor with the new method.
  • Adds two focused sync unit tests covering all four claim-shape combinations and explicitly asserting that team_id_default does not leak into SSO team assignments.

Confidence Score: 5/5

Change is safe to merge — it is a targeted, additive fix that adds a new method and updates two call-sites; no existing auth logic is modified.

The new get_all_jwt_team_ids method is well-scoped: it delegates to the existing get_team_ids_from_jwt for the plural field, directly reads the singular field with the same list-handling logic already present in get_team_id, and explicitly excludes team_id_default. The SSO changes are minimal two-line swaps. Two new sync tests cover all claim-shape permutations and guard against the team_id_default leakage concern raised in a prior review thread. No existing tests were modified or weakened.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/auth/handle_jwt.py Adds get_all_jwt_team_ids that unions team_ids_jwt_field (plural) and team_id_jwt_field (singular) without touching team_id_default; logic is consistent with the existing get_team_id behavior for list-valued singular fields
litellm/proxy/management_endpoints/ui_sso.py Two call-sites in generic_response_convertor switched from get_team_ids_from_jwt to get_all_jwt_team_ids; minimal, targeted change with no other logic altered
tests/test_litellm/proxy/auth/test_handle_jwt.py Adds two new sync unit tests covering all claim-shape combinations and explicitly guarding against team_id_default leakage; no existing tests modified

Reviews (2): Last reviewed commit: "fix(sso): don't propagate team_id_defaul..." | Re-trigger Greptile

Comment thread litellm/proxy/auth/handle_jwt.py Outdated
Comment thread tests/test_litellm/proxy/auth/test_handle_jwt.py Outdated
Greptile flagged that the helper was delegating to JWTHandler.get_team_id,
which falls back to litellm_jwtauth.team_id_default when the JWT claim is
missing or the field isn't configured. The plural-only get_team_ids_from_jwt
that the SSO path used historically has no such fallback, so threading
get_all_jwt_team_ids through generic_response_convertor would silently start
adding SSO users to the default team for any tenant that had team_id_default
configured — an undocumented backwards-incompatible change.

Read the singular team_id_jwt_field claim directly via get_nested_value
instead, so the helper returns only what's actually in the token. Added a
test that pins this behavior across both configurations of the default
(team_id_jwt_field set + missing claim, and team_id_jwt_field unset).

Also dropped @pytest.mark.asyncio from the sync test (P2 nit).
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review

@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot changed the base branch from litellm_internal_staging to litellm_agent_oss_staging_05_07_2026 May 7, 2026 01:26
@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot merged this pull request into litellm_agent_oss_staging_05_07_2026 May 7, 2026
112 of 114 checks passed
@oss-pr-review-agent-shin

Copy link
Copy Markdown
Contributor

🤖 litellm-agent: Squash-merged into staging branch litellm_agent_oss_staging_05_07_2026. Staging PR: #27359


Triage Summary
Adds a get_all_jwt_team_ids helper to JWTHandler that reads both the plural team_ids_jwt_field and singular team_id_jwt_field claims, then deduplicates them. Updates the SSO callback in ui_sso.py to call the new helper instead of the plural-only get_team_ids_from_jwt, fixing a silent bug where users whose IdP (Okta, Auth0) populates only the singular field were never added to their team. Includes unit tests covering singular-only, plural-only, overlap, and empty-token cases.

Merge Confidence: 5/5 ✅ READY
⚠️ 1 check failing: ci/circleci: local_testing_part1
Ready to ship.

Greptile 5/5, no blocking pattern findings, CircleCI passed. 1 check failing but unrelated to this diff: ci/circleci: local_testing_part1. 1 unrelated CI failure unique to this PR (ci/circleci: local_testing_part1) — not related to this diff but worth a glance.

@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot deleted the litellm_fix-sso-team-id-singular-claim branch May 7, 2026 01:26
oss-pr-review-agent-shin Bot added a commit that referenced this pull request May 7, 2026
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