{"record":{"id":"5ae0944ef3efd793","repo":"spring-projects/spring-security","slug":"oauth2error-from-authorization-response-error-dyn","errorCode":null,"errorMessage":"OAuth2Error from authorization response error (dynamic)","messagePattern":"OAuth2Error from authorization response error \\(dynamic\\)","errorType":"exception","errorClass":"OAuth2AuthorizationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java","lineNumber":81,"sourceCode":"\t * provided parameters.\n\t * @param accessTokenResponseClient the client used for requesting the access token\n\t * credential from the Token Endpoint\n\t */\n\tpublic OAuth2AuthorizationCodeAuthenticationProvider(\n\t\t\tOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {\n\t\tAssert.notNull(accessTokenResponseClient, \"accessTokenResponseClient cannot be null\");\n\t\tthis.accessTokenResponseClient = accessTokenResponseClient;\n\t}\n\n\t@Override\n\tpublic Authentication authenticate(Authentication authentication) throws AuthenticationException {\n\t\tOAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;\n\t\tOAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()\n\t\t\t.getAuthorizationResponse();\n\t\tif (authorizationResponse.statusError()) {\n\t\t\tOAuth2Error error = authorizationResponse.getError();\n\t\t\tAssert.notNull(error, \"error cannot be null when status is error\");\n\t\t\tthrow new OAuth2AuthorizationException(error);\n\t\t}\n\t\tOAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()\n\t\t\t.getAuthorizationRequest();\n\t\tif (!Objects.equals(authorizationResponse.getState(), authorizationRequest.getState())) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);\n\t\t\tthrow new OAuth2AuthorizationException(oauth2Error);\n\t\t}\n\t\tOAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(\n\t\t\t\tnew OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),\n\t\t\t\t\t\tauthorizationCodeAuthentication.getAuthorizationExchange()));\n\t\tOAuth2AuthorizationCodeAuthenticationToken authenticationResult = new OAuth2AuthorizationCodeAuthenticationToken(\n\t\t\t\tauthorizationCodeAuthentication.getClientRegistration(),\n\t\t\t\tauthorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(),\n\t\t\t\taccessTokenResponse.getRefreshToken(), accessTokenResponse.getAdditionalParameters());\n\t\tauthenticationResult.setDetails(authorizationCodeAuthentication.getDetails());\n\t\treturn authenticationResult;\n\t}\n","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java#L63-L99","documentation":"OAuth2AuthorizationCodeAuthenticationProvider handles the OAuth2 authorization-code callback. If the authorization response reports an error (statusError), it throws an OAuth2AuthorizationException carrying that error verbatim; if the state parameter does not match the original authorization request, it throws an OAuth2AuthorizationException with invalid_state_parameter. The message is dynamic because it comes from the authorization server's error redirect.","triggerScenarios":"authenticate(...) receives an OAuth2AuthorizationCodeAuthenticationToken whose authorization response has statusError() (e.g. error=access_denied) or whose state differs from the state stored in the authorization request; the provider throws OAuth2AuthorizationException with the upstream error or invalid_state_parameter.","commonSituations":"User denies consent at the provider (access_denied redirect); app restarted/scaled to another instance between request and callback so the AuthorizationRequestRepository (usually HttpSession) lost the stored state; load balancer without sticky sessions; cookies blocked, dropping the state attribute; registering the wrong redirect URI so the provider receives a malformed callback.","solutions":["Check the OAuth2Error error code in the exception — access_denied etc. is a user/provider decision, not a bug; handle it as a normal cancel.","For invalid_state_parameter, ensure the OAuth2AuthorizationRequestRepository (session/cookie) persists across the redirect: enable sticky sessions or a shared session store in multi-instance deployments.","Verify cookies are not blocked/stripped (SameSite/secure settings, proxy configuration) so state survives the round trip.","Confirm redirect-uri and provider metadata are correct so the callback hits the same app instance/session that started the flow."],"exampleFix":"// before: in-memory session lost behind LB with multiple instances\n// after: sticky sessions or distributed session\n// Spring Boot\nspring.session.store-type=redis\n// or at the LB: enable session affinity (sticky cookie) for the app instances","handlingStrategy":"try-catch","validationCode":"// validate the callback before invoking the provider\nOAuth2AuthorizationResponse resp = authorizationExchange.getAuthorizationResponse();\nboolean callbackOk = !resp.statusError()\n    && Objects.equals(resp.getState(), authorizationExchange.getAuthorizationRequest().getState());","typeGuard":"boolean isRecoverableAuthorizationError(OAuth2AuthorizationException ex) {\n    return \"access_denied\".equals(ex.getError().getErrorCode())\n        || \"invalid_state_parameter\".equals(ex.getError().getErrorCode());\n}","tryCatchPattern":"try {\n    return authenticationManager.authenticate(authorizationCodeAuthentication);\n} catch (OAuth2AuthorizationException ex) {\n    if (\"access_denied\".equals(ex.getError().getErrorCode())) {\n        // user declined consent — show friendly cancel page\n    } else if (\"invalid_state_parameter\".equals(ex.getError().getErrorCode())) {\n        // restart the login flow; check session stickiness/cookies\n    }\n}","preventionTips":["Use sticky sessions or a distributed session/AuthorizationRequestRepository in multi-instance deployments.","Ensure cookies survive the redirect (correct SameSite/secure/proxy settings).","Handle access_denied gracefully — it is a normal user action, not a system fault."],"tags":["oauth2","authorization-code","state-parameter","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-14T11:17:12.474Z"}