Skip to content

feat(auth_v2): define admin team routes in backend/routers on Security DI - #30389

Draft
yassin-berriai wants to merge 8 commits into
litellm_fix/auth-modulefrom
litellm_admin_auth_v2
Draft

feat(auth_v2): define admin team routes in backend/routers on Security DI#30389
yassin-berriai wants to merge 8 commits into
litellm_fix/auth-modulefrom
litellm_admin_auth_v2

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Stacked on top of #30171 (targets litellm_fix/auth-module, not main/litellm_internal_staging).

Linear ticket

None

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

🆕 New Feature

Changes

Today the backend component reuses the whole proxy app and trims it down to the admin surface by a path allowlist; the admin routes themselves still live in the proxy and authenticate through the legacy user_api_key_auth dependency. This starts moving that surface toward explicit, backend-owned routers that authenticate through the auth_v2 AuthSecurity Security layer, with the teams vertical as the first one done end to end.

What changed:

  • New backend/routers package. Its teams router is the source of truth for the admin teams surface (POST/GET/PUT/DELETE /admin/teams, list, and membership). proxy_server imports and mounts it, so the route definitions live in backend/routers and the proxy includes them rather than owning them. The include is guarded by the backend package being importable, because the pip wheel ships only litellm while the source tree and Docker image ship backend too; pip-installed proxies simply do not mount the admin routes.
  • Every route authenticates through request.app.state.auth_v2 (a require_roles(ORG_ADMIN, PLATFORM_ADMIN) gate over the resolved Principal) instead of user_api_key_auth. app.state.auth_v2 is built in the proxy startup once the DB is connected (an AuthSecurity over the DbIdentityStore).
  • backend/main keeps the explicit admin routes regardless of the allowlist; the allowlist still covers the rest of the not-yet-migrated management surface, so nothing is dropped during the migration.

Two resolver fixes were needed to make the DB-backed path actually work, since DbIdentityStore's write path had only ever been exercised against an in-memory store:

  • API-key principals now resolve their platform role from the owning user. get_key_object does not join user_role onto the token, so an admin virtual key was coming back role-less and could never pass the role gate.
  • Team group writes wrap members_with_roles in prisma Json so upsert_group persists, and db_team_to_scim now carries members so team membership round-trips on read (this also fixes SCIM GET /Groups returning empty membership).

Scope note: this is deliberately one vertical. The remaining management endpoints are tightly coupled to UserAPIKeyAuth (budget/limit/permission state that Principal is identity-only by design and does not carry), so they migrate onto this pattern incrementally in follow-ups rather than in a single bulk rewrite.

Tests

  • tests/test_litellm/proxy/test_backend_admin_teams.py: drives the router through a real AuthSecurity and asserts the gate (401 unauthenticated, 401 unknown key, 403 without admin role) and CRUD + membership round-trip.
  • tests/test_litellm/proxy/auth_v2/test_resolver.py: regression that an API key with a user_id but no user_role resolves the role from the user table, plus DbIdentityStore group CRUD round-tripping members through prisma Json.
  • tests/test_litellm/proxy/auth_v2/test_utils.py: db_team_to_scim carries members.
  • tests/test_litellm/proxy/test_component_allowlists.py: updated so the gateway/backend coverage union accounts for the explicit /admin routes.

Screenshots / Proof of Fix

Ran against a live backend (uvicorn backend.main:app on :4001) backed by a real Postgres. An admin user+key (proxy_admin) and a reader user+key (internal_user) were created via /user/new, then:

### 1. No credential -> 401
HTTP/1.1 401 Unauthorized
{"detail":"Not authenticated"}

### 2. Reader key (internal_user, no admin role) -> 403
HTTP/1.1 403 Forbidden
{"detail":"Insufficient role"}

### 3. Admin key create team -> 201
{"id":"98f7ddbd-b60a-4fd8-b1da-42340ff4859f","name":"eng","members":["admin-user","reader-user"]}

### GET team (membership round-trips from DB)
{"id":"98f7ddbd-...","name":"eng","members":["admin-user","reader-user"]}

### LIST teams
[{"id":"98f7ddbd-...","name":"eng","members":["admin-user","reader-user"]}]

### PUT update membership -> [reader-user]
{"id":"98f7ddbd-...","name":"eng-renamed","members":["reader-user"]}

### DELETE -> 204
HTTP 204
### GET after delete -> 404
HTTP 404

A team created via /admin/teams is a real DB team visible to the legacy endpoint (shared store):

$ curl -s "http://127.0.0.1:4001/team/info?team_id=$NEW" -H "Authorization: Bearer sk-1234"
team_alias= shared-store-check members_with_roles= [{'user_id': 'admin-user', 'user_email': None, 'role': 'user'}]

And the admin routes show up on the proxy app's OpenAPI (source of truth):

$ curl -s http://127.0.0.1:4001/openapi.json | jq '.paths | keys | map(select(startswith("/admin")))'
["/admin/teams", "/admin/teams/{team_id}"]

@CLAassistant

CLAassistant commented Jun 13, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
0 out of 2 committers have signed the CLA.

❌ yassin-berriai
❌ claude
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/auth_v2/resolvers.py 84.61% 6 Missing ⚠️
litellm/proxy/proxy_server.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@yassin-berriai
yassin-berriai force-pushed the litellm_admin_auth_v2 branch from a32f6c7 to 4a2c689 Compare June 13, 2026 21:47
claude added 3 commits June 13, 2026 22:00
…y DI

Move the admin surface toward explicit, backend-owned routers instead of
trimming the proxy app by path allowlist. This adds a backend/routers package
whose teams router is the source of truth for /admin/teams CRUD plus membership;
proxy_server imports and mounts it (guarded by the backend package being
importable, since the pip wheel ships only litellm), and backend/main keeps
those explicit routes regardless of the allowlist.

Every route authenticates through the auth_v2 AuthSecurity stored on
app.state.auth_v2 (a require_roles gate over the Principal) rather than the
legacy user_api_key_auth dependency; app.state.auth_v2 is wired in the proxy
startup once the DB is connected.

Two resolver fixes were needed to make the DB-backed path actually work, since
it was previously only exercised against an in-memory store: API-key principals
now resolve their platform role from the owning user (get_key_object does not
join user_role onto the token), and team group writes wrap members_with_roles
in prisma Json so upsert_group persists. db_team_to_scim now carries members so
team membership round-trips on read.
…ypes

Regression tests for upsert_group/get_group/list_groups/delete_group against a
fake prisma that mirrors the one behavior they depend on (a Json-wrapped write
round-trips as its plain value), so a future drop of the prisma Json wrapper or
the typed read fails here rather than only against a live database.

Regenerate ui/litellm-dashboard/src/lib/http/schema.d.ts from the proxy
OpenAPI spec now that the /admin/teams routes are mounted.
@yassin-berriai
yassin-berriai force-pushed the litellm_admin_auth_v2 branch from 4a2c689 to 1f6506f Compare June 13, 2026 22:02
claude and others added 5 commits June 13, 2026 22:09
…tellm_admin_auth_v2

# Conflicts:
#	backend/main.py
#	litellm/proxy/auth_v2/resolvers.py
#	litellm/proxy/proxy_server.py
#	tests/test_litellm/proxy/auth_v2/test_resolver.py
#	tests/test_litellm/proxy/test_component_allowlists.py
@yassin-berriai
yassin-berriai force-pushed the litellm_admin_auth_v2 branch from d052811 to fa6a94c Compare July 17, 2026 20:56
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.

3 participants