fix(proxy): auth_v2 JWT node falls through cleanly when unconfigured

A token shaped like a JWT (non sk-, two dots) was claimed by the JWT
authenticator even when no jwks_uri was configured, producing a 500 from the
settings loader. It now only claims JWT-shaped tokens when JWT auth is actually
configured, so an unconfigured deployment ends the chain in a clean 401.
This commit is contained in:
ryan-crabbe-berri 2026-06-05 08:54:22 -07:00
parent 2a85db9a91
commit 940791576d
2 changed files with 42 additions and 2 deletions

View file

@ -101,15 +101,28 @@ def _load_jwt_settings() -> Any:
)
def _jwt_is_configured() -> bool:
import os
from litellm.proxy.proxy_server import general_settings
cfg = (general_settings or {}).get("auth_v2_jwt") or {}
return bool(cfg.get("jwks_uri") or os.getenv("AUTH_V2_JWKS_URI"))
class JWTAuthenticator:
"""Verifies a bearer JWT with authlib and maps its claims to an identity."""
def can_handle(self, api_key: Optional[str]) -> bool:
return (
# Only claim JWT-shaped tokens when JWT auth is actually configured, so an
# unconfigured deployment falls through to a clean 401 instead of a 500.
if not (
isinstance(api_key, str)
and not api_key.startswith("sk-")
and api_key.count(".") == 2
)
):
return False
return _jwt_is_configured()
async def authenticate(self, api_key: str, ctx: AuthContext) -> Any:
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth

View file

@ -1,10 +1,13 @@
import pytest
from litellm.proxy.auth.v2.authenticators import (
JWTAuthenticator,
MasterKeyAuthenticator,
VirtualKeyAuthenticator,
)
JWT_SHAPED = "header.payload.signature"
MASTER = "sk-master-secret-123"
@ -40,3 +43,27 @@ def test_virtual_key_handles_sk_prefix_but_not_master_first():
assert vk.can_handle("sk-abc123") is True
assert vk.can_handle("not-a-key") is False
assert vk.can_handle(None) is False
def test_jwt_authenticator_inert_when_unconfigured(monkeypatch):
# No auth_v2_jwt config and no env var: a JWT-shaped token must fall through
# (can_handle False) so the chain ends in a clean 401, not a 500.
monkeypatch.setattr(
"litellm.proxy.proxy_server.general_settings", {}, raising=False
)
monkeypatch.delenv("AUTH_V2_JWKS_URI", raising=False)
assert JWTAuthenticator().can_handle(JWT_SHAPED) is False
def test_jwt_authenticator_handles_jwt_shape_when_configured(monkeypatch):
monkeypatch.setattr(
"litellm.proxy.proxy_server.general_settings",
{"auth_v2_jwt": {"jwks_uri": "https://idp.example/jwks"}},
raising=False,
)
auth = JWTAuthenticator()
assert auth.can_handle(JWT_SHAPED) is True
# Still only claims JWT-shaped tokens, never virtual keys or non-3-part values.
assert auth.can_handle("sk-abc.def.ghi") is False
assert auth.can_handle("not.a.jwt.token") is False
assert auth.can_handle(None) is False