BerriAI/litellm · error · Exception
Invalid JWT Submitted
Error message
Invalid JWT Submitted
What it means
Raised at the end of auth_jwt when get_public_key returned None instead of a key or an exception: no usable public key was resolved for the token, so verification cannot even be attempted and the token is rejected as 'Invalid JWT Submitted'. Practically this surfaces when the key-lookup path yields nothing - e.g. an empty/blank JWT_PUBLIC_KEY_URL value that produces an empty URL list, or a JWKS response whose parse_keys found nothing to return.
Source
Thrown at litellm/proxy/auth/handle_jwt.py:1039
token=token,
public_key=public_key,
audience=decode_kwargs["audience"],
issuer=decode_kwargs["issuer"],
options=decode_kwargs["options"],
)
return {k: v for k, v in payload.items() if k not in self.LITELLM_INTERNAL_CLAIMS}
except jwt.ExpiredSignatureError:
raise ProxyException(
message="Token Expired",
type=ProxyErrorTypes.expired_key,
param=None,
code=status.HTTP_401_UNAUTHORIZED,
)
except Exception as e:
raise Exception(f"Validation fails: {e}")
raise Exception("Invalid JWT Submitted")
async def close(self):
await self.http_handler.close()
class JWTAuthManager:
"""Manages JWT authentication and authorization operations"""
@staticmethod
def can_rbac_role_call_route(
rbac_role: RBAC_ROLES,
general_settings: dict,
route: str,
) -> Literal[True]:
"""
Checks if user is allowed to access the route, based on their role.
"""
role_based_routes: Final = get_role_based_routes(rbac_role=rbac_role, general_settings=general_settings)View on GitHub (pinned to 77b7c6c40c)
Solutions
- Ensure JWT_PUBLIC_KEY_URL is set to a non-empty, valid JWKS or discovery URL (check for stray whitespace or commas)
- curl the JWKS URL and confirm it returns at least one key (a non-empty keys array)
- Prefer configuring keys via litellm_jwtauth in the config YAML if env-var handling is unreliable in your deployment
Example fix
# before: whitespace-only value yields an empty URL list JWT_PUBLIC_KEY_URL=" , " # after: a real JWKS endpoint JWT_PUBLIC_KEY_URL=https://idp.example.com/protocol/openid-connect/certs
Defensive patterns
Strategy: validation
Validate before calling
import os
def jwt_key_sources_are_configured() -> None:
raw = os.getenv("JWT_PUBLIC_KEY_URL", "")
urls = [u.strip() for u in raw.split(",") if u.strip()]
if not urls:
raise ValueError("JWT_PUBLIC_KEY_URL unset or blank - configure a JWKS/discovery URL") Prevention
- Lint JWT_PUBLIC_KEY_URL for empty/whitespace values in deployment checks
- Canary the JWKS endpoint at startup to confirm it returns at least one key
- Prefer config-yaml litellm_jwtauth public-key settings over loosely validated env strings
When it happens
Trigger: JWT auth is active without issuer_configs, get_public_key(kid) resolves to None (blank env var entries, empty keys payload), and the code falls through the 'if public_key is not None' block to this raise.
Common situations: JWT_PUBLIC_KEY_URL set but containing only whitespace/commas so the URL list is empty; JWKS endpoint returning an empty keys structure; defensive fallback after partially-failed key resolution.
Related errors
- JWT Auth: OIDC discovery endpoint {url} returned status {res
- JWT Auth: OIDC discovery document at {url} does not contain
- Error parsing response: {e}. Check server logs for original
- No matching public key found. keys={resolved_jwks_url}, kid=
- Missing JWT Public Key URL from environment.
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/7de5241dfe691199.
Report an issue: GitHub.