mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
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:
parent
2a85db9a91
commit
940791576d
2 changed files with 42 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue