{"record":{"id":"27ca13de21bb11f7","repo":"spring-projects/spring-security","slug":"invalid-id-token-27ca13","errorCode":"invalid_id_token","errorMessage":"${ex.getMessage()} (JwtException during ID Token decode)","messagePattern":"(.+?) \\(JwtException during ID Token decode\\)","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java","lineNumber":229,"sourceCode":"\t}\n\n\tprivate OidcIdToken createOidcToken(ClientRegistration clientRegistration,\n\t\t\tOAuth2AccessTokenResponse accessTokenResponse) {\n\t\tJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);\n\t\tJwt jwt = getJwt(accessTokenResponse, jwtDecoder);\n\t\treturn new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());\n\t}\n\n\tprivate Jwt getJwt(OAuth2AccessTokenResponse accessTokenResponse, JwtDecoder jwtDecoder) {\n\t\ttry {\n\t\t\tMap<String, Object> parameters = accessTokenResponse.getAdditionalParameters();\n\t\t\tString idToken = (String) parameters.get(OidcParameterNames.ID_TOKEN);\n\t\t\tAssert.hasText(idToken, \"id_token parameter cannot be null or empty\");\n\t\t\treturn jwtDecoder.decode(idToken);\n\t\t}\n\t\tcatch (JwtException ex) {\n\t\t\tOAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);\n\t\t\tthrow new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);\n\t\t}\n\t}\n\n\tprivate void validateIdToken(OidcUser existingOidcUser, OidcIdToken idToken) {\n\t\t// OpenID Connect Core 1.0 - Section 12.2 Successful Refresh Response\n\t\t// If an ID Token is returned as a result of a token refresh request, the\n\t\t// following requirements apply:\n\t\t// its iss Claim Value MUST be the same as in the ID Token issued when the\n\t\t// original authentication occurred,\n\t\tvalidateIssuer(existingOidcUser, idToken);\n\t\t// its sub Claim Value MUST be the same as in the ID Token issued when the\n\t\t// original authentication occurred,\n\t\tvalidateSubject(existingOidcUser, idToken);\n\t\t// its iat Claim MUST represent the time that the new ID Token is issued,\n\t\tvalidateIssuedAt(existingOidcUser, idToken);\n\t\t// its aud Claim Value MUST be the same as in the ID Token issued when the\n\t\t// original authentication occurred,\n\t\tvalidateAudience(existingOidcUser, idToken);","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java#L211-L247","documentation":"During an OIDC refresh-token flow, the OidcAuthorizedClientRefreshedEventListener decodes the new id_token returned by the token endpoint using the configured JwtDecoder. If decoding fails with any JwtException (expired signature, malformed token, bad signature, etc.), the listener wraps the exception's message in an OAuth2AuthenticationException with error code 'invalid_id_token'. This means the refreshed ID Token could not be validated cryptographically or structurally before the per-claim checks run.","triggerScenarios":"A refresh token response returns an id_token that fails JwtDecoder.decode: expired iat/exp outside clock skew, signature verification failure (wrong keys/JWKS unreachable), malformed JWT, or missing claims required by the validator chain.","commonSituations":"Provider rotates signing keys and the cached JWKS is stale; the refresh response omits id_token but a non-OIDC flow is misconfigured; clock skew between client and IdP exceeds the validator's tolerance; custom JwtValidator rejects the token; the wrong decoder is registered for the registration.","solutions":["Check the nested JwtException cause in the logs — it names the exact decode failure (signature, expiry, malformed).","Ensure the JwtDecoder's JWKS cache can fetch the provider's current signing keys (verify jwkSetUri is reachable and keys were not rotated).","Increase the clock skew on the decoder's validators, e.g. JwtTimestampValidator(Duration.ofSeconds(60)), if the failure is exp/iat tolerance.","Decode the returned id_token manually (jwt.io or Nimbus) to inspect claims and confirm what the provider actually sent.","If the provider does not return an id_token on refresh, confirm your ClientRegistration is OIDC-scoped and handle the null id_token path instead of forcing decode."],"exampleFix":"// before\nJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();\n// after\nJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)\n    .decoders-Alg(SignatureAlgorithm.RS256)\n    .build();\n// and relax clock skew when needed:\nOAuth2TokenValidator<Jwt> withSkew = new DelegatingOAuth2TokenValidator<>(\n    new JwtTimestampValidator(Duration.ofSeconds(60)),\n    new OidcIdTokenValidator(clientRegistration));\n((NimbusJwtDecoder) decoder).setJwtValidator(withSkew);","handlingStrategy":"try-catch","validationCode":"// pre-check: decode before refresh commit\ntry { jwtDecoder.decode(idToken); } catch (JwtException e) { /* abort refresh */ }","typeGuard":null,"tryCatchPattern":"try {\n    listener.onApplicationEvent(event);\n} catch (OAuth2AuthenticationException ex) {\n    if (\"invalid_id_token\".equals(ex.getError().getErrorCode())) {\n        authorizedClientService.removeAuthorizedClient(registrationId, principalName);\n        // force re-authentication\n    }\n    logger.warn(\"ID token decode failed: {}\", ex.getCause(), ex);\n}","preventionTips":["Log the JwtException cause to identify signature vs expiry failures","Keep JWKS endpoints reachable and let NimbusJwtDecoder refresh keys","Configure clock skew on JwtTimestampValidator","Pre-validate refresh responses contain an id_token when OIDC scope is used"],"tags":["oauth2","oidc","jwt","token-refresh"],"backgroundTag":"jwt-validation-failed","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}