BerriAI/litellm · error · Exception

Validation fails: {e}

Error message

Validation fails: {e}

What it means

Raised in _auth_jwt_with_issuer as the generic decode-failure handler: PyJWT rejected the token for any reason other than expiry - most commonly InvalidSignatureError (wrong public key), InvalidAudienceError (aud mismatch with issuer_config.audience), InvalidIssuerError (iss mismatch), or DecodeError (malformed token). The underlying PyJWT exception text is appended after 'Validation fails: ', naming the exact check that failed.

Source

Thrown at litellm/proxy/auth/handle_jwt.py:992

            kid=kid,
        )
        try:
            payload: Final = self._decode_jwt_with_public_key(
                token=token,
                public_key=public_key,
                audience=issuer_config.audience,
                issuer=issuer_config.issuer,
                disable_audience_validation=issuer_config.disable_audience_validation,
            )
        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}")

        return self._apply_issuer_claim_mappings(
            token=payload,
            issuer_config=issuer_config,
        )

    async def auth_jwt(self, token: str) -> dict:
        header: Final = jwt.get_unverified_header(token)

        verbose_proxy_logger.debug("header: %s", header)

        kid: Final = header.get("kid", None)

        issuer_config: Final = self._get_configured_issuer(token=token)
        if issuer_config is not None:
            return await self._auth_jwt_with_issuer(
                token=token,
                issuer_config=issuer_config,

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Read the suffix after 'Validation fails:' - 'Signature verification failed', 'Audience doesn\u2019t match', 'Invalid issuer' each point to a different fix
  2. Decode the token (jwt.io) and compare its aud and iss against the issuer_config's audience/issuer values; align them
  3. For signature failures, confirm the token's kid resolves to the key you configured and that the JWKS is current post-rotation
  4. If multiple issuers are configured, check which issuer block matched (its audience/issuer) - the token may need to be sent with a config whose values match its claims

Example fix

# config.yaml - before: audience does not match the token's aud claim
litellm_jwtauth:
  issuer_configs:
    - issuer: https://idp.example.com
      audience: my-api-v1

# config.yaml - after: audience matches the token
litellm_jwtauth:
  issuer_configs:
    - issuer: https://idp.example.com
      audience: https://my-api.example.com/v2
Defensive patterns

Strategy: validation

Validate before calling

import jwt as pyjwt

def token_matches_issuer_config(token: str, audience: str, issuer: str) -> None:
    payload = pyjwt.decode(token, options={"verify_signature": False})
    if audience and payload.get("aud") not in (audience if isinstance(audience, list) else [audience]):
        raise ValueError(f"token aud={payload.get('aud')!r} does not match configured audience={audience!r}")
    if issuer and payload.get("iss") != issuer:
        raise ValueError(f"token iss={payload.get('iss')!r} does not match configured issuer={issuer!r}")

Try / catch

# the PyJWT reason is appended after 'Validation fails:' - branch on it
try:
    await call_proxy(bearer_token)
except Exception as e:
    msg = str(e)
    if "Validation fails:" in msg:
        if "Audience" in msg:
            fix_audience_config()      # align issuer_config.audience with token aud
        elif "Signature" in msg:
            await refresh_jwks()       # stale/wrong signing key
        else:
            raise
    else:
        raise

Prevention

When it happens

Trigger: A JWT verified against an issuer_config whose public key did not sign the token, or whose audience/issuer settings do not match the token's aud/iss claims - e.g. a token minted for a different client_id sent where audience is that client_id, or the wrong issuer block matched the token.

Common situations: audience config drift after the IdP changes the API identifier; tokens from a different OIDC realm/application sharing an IdP; JWKS serving stale keys after rotation so signature verification fails; hand-edited tokens failing signature or format checks.

Related errors


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