{"record":{"id":"1037d607bed8fc49","repo":"spring-projects/spring-security","slug":"invalid-grant","errorCode":"invalid_grant","errorMessage":"Client authentication failed: code_verifier","messagePattern":"Client authentication failed: code_verifier","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java","lineNumber":69,"sourceCode":" * @see OAuth2ClientAuthenticationToken\n * @see OAuth2AuthorizationService\n */\nfinal class CodeVerifierAuthenticator {\n\n\tprivate static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);\n\n\tprivate final Log logger = LogFactory.getLog(getClass());\n\n\tprivate final OAuth2AuthorizationService authorizationService;\n\n\tCodeVerifierAuthenticator(OAuth2AuthorizationService authorizationService) {\n\t\tAssert.notNull(authorizationService, \"authorizationService cannot be null\");\n\t\tthis.authorizationService = authorizationService;\n\t}\n\n\tvoid authenticateRequired(OAuth2ClientAuthenticationToken clientAuthentication, RegisteredClient registeredClient) {\n\t\tif (!authenticate(clientAuthentication, registeredClient)) {\n\t\t\tthrow invalidGrantException(PkceParameterNames.CODE_VERIFIER);\n\t\t}\n\t}\n\n\tvoid authenticateIfAvailable(OAuth2ClientAuthenticationToken clientAuthentication,\n\t\t\tRegisteredClient registeredClient) {\n\t\tauthenticate(clientAuthentication, registeredClient);\n\t}\n\n\tprivate boolean authenticate(OAuth2ClientAuthenticationToken clientAuthentication,\n\t\t\tRegisteredClient registeredClient) {\n\n\t\tMap<String, Object> parameters = clientAuthentication.getAdditionalParameters();\n\t\tif (!authorizationCodeGrant(parameters)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tString code = (String) parameters.get(OAuth2ParameterNames.CODE);\n\t\tAssert.hasText(code, \"code cannot be empty\");","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java#L51-L87","documentation":"CodeVerifierAuthenticator.authenticateRequired verifies the PKCE code_verifier when the authorization grant requires it (public clients using authorization_code + PKCE). If the client authentication does not include/validate a code_verifier matching the stored code_challenge, invalid_grant with the 'code_verifier' detail is thrown on the token endpoint.","triggerScenarios":"Token request for a PKCE authorization_code grant (public client) processed by CodeVerifierAuthenticator.authenticateRequired where authenticate() returns false — missing code_verifier parameter, or the provided verifier does not hash to the stored code_challenge.","commonSituations":"SPA/mobile clients omitting code_verifier in the token request; verifier generated with a different transformation than the challenge (S256 vs plain); code_challenge stored at authorization time but verifier lost across redirect; parameters sent in the wrong request (auth request vs token request).","solutions":["Include code_verifier in the token request body and ensure it is the exact original string used to derive the code_challenge (for S256: BASE64URL(SHA256(verifier))).","Persist the verifier client-side between the authorization request and the token exchange (session storage, keychain, secure storage) — do not regenerate it.","Use a RFC 7636-compliant PKCE library/helper to generate verifier (43-128 chars, unreserved chars) and challenge consistently.","Confirm the same client_id is used at both endpoints and the authorization code has not expired or been consumed."],"exampleFix":"// before\n// token request without PKCE params\nPOST /oauth2/token  grant_type=authorization_code&code=abc&client_id=spa&redirect_uri=...\n// after\nPOST /oauth2/token  grant_type=authorization_code&code=abc&client_id=spa&redirect_uri=...&code_verifier=<original-verifier>","handlingStrategy":"validation","validationCode":"// client side: verify verifier before token exchange\nString verifier = session.getAttribute(\"pkce_verifier\");\nif (verifier == null || verifier.length() < 43 || verifier.length() > 128) {\n    throw new IllegalStateException(\"PKCE code_verifier missing or invalid length\");\n}\n// ensure it is appended to the token request body","typeGuard":null,"tryCatchPattern":"try {\n    exchangeCodeForToken(code, verifier);\n} catch (OAuth2ErrorResponseException e) {\n    if (\"invalid_grant\".equals(e.getError())) {\n        // restart the full PKCE flow: new authorization request with fresh verifier/challenge\n    }\n}","preventionTips":["Persist the code_verifier for the lifetime of the authorization flow (session/secure storage).","Generate challenges with the same S256 transformation the server expects.","Use a maintained OAuth2/OIDC client library that handles PKCE automatically.","Never reuse authorization codes or verifiers across flows."],"tags":["oauth2","pkce","invalid-grant","spring-authorization-server"],"backgroundTag":"invalid-credentials","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}