{"record":{"id":"44b49b3900931362","repo":"spring-projects/spring-security","slug":"oauth2error-from-upstream-authorization-exception-44b49b","errorCode":null,"errorMessage":"OAuth2Error from upstream authorization exception (dynamic)","messagePattern":"OAuth2Error from upstream authorization exception \\(dynamic\\)","errorType":"exception","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java","lineNumber":183,"sourceCode":"\t\tCollection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper\n\t\t\t.mapAuthorities(oidcUser.getAuthorities());\n\t\tOAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(\n\t\t\t\tauthorizationCodeAuthentication.getClientRegistration(),\n\t\t\t\tauthorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,\n\t\t\t\taccessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());\n\t\tauthenticationResult.setDetails(authorizationCodeAuthentication.getDetails());\n\t\treturn authenticationResult;\n\t}\n\n\tprivate OAuth2AccessTokenResponse getResponse(OAuth2LoginAuthenticationToken authorizationCodeAuthentication) {\n\t\ttry {\n\t\t\treturn this.accessTokenResponseClient.getTokenResponse(\n\t\t\t\t\tnew OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),\n\t\t\t\t\t\t\tauthorizationCodeAuthentication.getAuthorizationExchange()));\n\t\t}\n\t\tcatch (OAuth2AuthorizationException ex) {\n\t\t\tOAuth2Error oauth2Error = ex.getError();\n\t\t\tthrow new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);\n\t\t}\n\t}\n\n\tprivate void validateNonce(OAuth2AuthorizationRequest authorizationRequest, OidcIdToken idToken) {\n\t\tString requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);\n\t\tif (requestNonce == null) {\n\t\t\treturn;\n\t\t}\n\t\tString nonceHash = getNonceHash(requestNonce);\n\t\tString nonceHashClaim = idToken.getNonce();\n\t\tif (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);\n\t\t\tthrow new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());\n\t\t}\n\t}\n\n\tprivate String getNonceHash(String requestNonce) {\n\t\ttry {","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java#L165-L201","documentation":"OidcAuthorizationCodeAuthenticationProvider.getResponse() calls the configured accessTokenResponseClient to exchange the code. Any OAuth2AuthorizationException raised there (network error, invalid_token_response, token endpoint rejection) is converted into an OAuth2AuthenticationException whose message is the upstream OAuth2Error's toString. The real cause is in the wrapped error/cause.","triggerScenarios":"Thrown in getResponse() (invoked from authenticate via accessTokenResponse) when the access-token request for the OIDC authorization-code grant fails: HTTP error from the token endpoint, empty body, connection failure, or client-authentication failure.","commonSituations":"Wrong token URI, expired/already-redeemed authorization code (browser refresh or back-button), client authentication mismatch (basic vs post vs private_key_jwt), or the OP returning invalid_grant.","solutions":["Inspect ex.getError() (code + description) — typical values are invalid_grant, invalid_client, invalid_token_response.","For invalid_grant: prevent code reuse/replays (don't re-post the login callback) and check clock skew between app and OP.","For invalid_client: fix client credentials and clientAuthenticationMethod.","For invalid_token_response: verify the token endpoint returns proper JSON (see empty/malformed response fixes)."],"exampleFix":"// before: user refreshes callback URL -> invalid_grant surfaces as 500\n// after: catch and redirect to a re-login flow\ncatch (OAuth2AuthenticationException ex) {\n    if (\"invalid_grant\".equals(ex.getError().getErrorCode())) {\n        return \"redirect:/oauth2/authorization/\" + registrationId;\n    }\n    throw ex;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (OAuth2AuthenticationException ex) {\n    String code = ex.getError().getErrorCode();\n    if (\"invalid_grant\".equals(code)) { return \"redirect:/oauth2/authorization/\" + registrationId; }\n    if (\"invalid_client\".equals(code)) { throw new ConfigurationException(\"Check client credentials/auth method\", ex); }\n    throw ex;\n}","preventionTips":["Never let users bookmark/refresh the OAuth2 callback URL; codes are single-use.","Keep app and OP clocks synchronized (NTP).","Align clientAuthenticationMethod and credentials with the OP's requirements.","Monitor token-endpoint error codes to distinguish config bugs from transient failures."],"tags":["oidc","oauth2","token-exchange","spring-security"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}