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

  1. Ensure JWT_PUBLIC_KEY_URL is set to a non-empty, valid JWKS or discovery URL (check for stray whitespace or commas)
  2. curl the JWKS URL and confirm it returns at least one key (a non-empty keys array)
  3. 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

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


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/7de5241dfe691199. Report an issue: GitHub.