{"record":{"id":"761b08a2ae8d7b8c","repo":"BerriAI/litellm","slug":"validation-fails-e","errorCode":null,"errorMessage":"Validation fails: {e}","messagePattern":"Validation fails: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/proxy/auth/handle_jwt.py","lineNumber":992,"sourceCode":"            kid=kid,\n        )\n        try:\n            payload: Final = self._decode_jwt_with_public_key(\n                token=token,\n                public_key=public_key,\n                audience=issuer_config.audience,\n                issuer=issuer_config.issuer,\n                disable_audience_validation=issuer_config.disable_audience_validation,\n            )\n        except jwt.ExpiredSignatureError:\n            raise ProxyException(\n                message=\"Token Expired\",\n                type=ProxyErrorTypes.expired_key,\n                param=None,\n                code=status.HTTP_401_UNAUTHORIZED,\n            )\n        except Exception as e:\n            raise Exception(f\"Validation fails: {e}\")\n\n        return self._apply_issuer_claim_mappings(\n            token=payload,\n            issuer_config=issuer_config,\n        )\n\n    async def auth_jwt(self, token: str) -> dict:\n        header: Final = jwt.get_unverified_header(token)\n\n        verbose_proxy_logger.debug(\"header: %s\", header)\n\n        kid: Final = header.get(\"kid\", None)\n\n        issuer_config: Final = self._get_configured_issuer(token=token)\n        if issuer_config is not None:\n            return await self._auth_jwt_with_issuer(\n                token=token,\n                issuer_config=issuer_config,","sourceCodeStart":974,"sourceCodeEnd":1010,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/auth/handle_jwt.py#L974-L1010","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the suffix after 'Validation fails:' - 'Signature verification failed', 'Audience doesn\\u2019t match', 'Invalid issuer' each point to a different fix","Decode the token (jwt.io) and compare its aud and iss against the issuer_config's audience/issuer values; align them","For signature failures, confirm the token's kid resolves to the key you configured and that the JWKS is current post-rotation","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"],"exampleFix":"# config.yaml - before: audience does not match the token's aud claim\nlitellm_jwtauth:\n  issuer_configs:\n    - issuer: https://idp.example.com\n      audience: my-api-v1\n\n# config.yaml - after: audience matches the token\nlitellm_jwtauth:\n  issuer_configs:\n    - issuer: https://idp.example.com\n      audience: https://my-api.example.com/v2","handlingStrategy":"validation","validationCode":"import jwt as pyjwt\n\ndef token_matches_issuer_config(token: str, audience: str, issuer: str) -> None:\n    payload = pyjwt.decode(token, options={\"verify_signature\": False})\n    if audience and payload.get(\"aud\") not in (audience if isinstance(audience, list) else [audience]):\n        raise ValueError(f\"token aud={payload.get('aud')!r} does not match configured audience={audience!r}\")\n    if issuer and payload.get(\"iss\") != issuer:\n        raise ValueError(f\"token iss={payload.get('iss')!r} does not match configured issuer={issuer!r}\")","typeGuard":null,"tryCatchPattern":"# the PyJWT reason is appended after 'Validation fails:' - branch on it\ntry:\n    await call_proxy(bearer_token)\nexcept Exception as e:\n    msg = str(e)\n    if \"Validation fails:\" in msg:\n        if \"Audience\" in msg:\n            fix_audience_config()      # align issuer_config.audience with token aud\n        elif \"Signature\" in msg:\n            await refresh_jwks()       # stale/wrong signing key\n        else:\n            raise\n    else:\n        raise","preventionTips":["Keep issuer_config audience/issuer values in lockstep with the IdP's aud/iss - alert on IdP-side claim changes","Verify a sample token against the issuer config in CI before shipping config changes","After key rotation, confirm the JWKS actually serves the new signing key"],"tags":["jwt","signature","audience","issuer","validation","authentication"],"backgroundTag":"jwt-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}