{"record":{"id":"3419064c2ccfc32d","repo":"apereo/cas","slug":"invalid-grant","errorCode":"invalid_grant","errorMessage":"Access token validation failed for request [{}]","messagePattern":"Access token validation failed for request \\[(.+?)\\]","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20AccessTokenEndpointController.java","lineNumber":99,"sourceCode":"\n    /**\n     * Handle request internal model and view.\n     *\n     * @param request  the request\n     * @param response the response\n     * @return the model and view\n     * @throws Exception the exception\n     */\n    @PostMapping(path = {\n        OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.ACCESS_TOKEN_URL,\n        OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.TOKEN_URL},\n        produces = MediaType.APPLICATION_JSON_VALUE)\n    @Operation(summary = \"Handle OAuth access token request\")\n    public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception {\n        val context = new JEEContext(request, response);\n        try {\n            if (!verifyAccessTokenRequest(context)) {\n                LOGGER.warn(\"Access token validation failed for request [{}]\", context.getFullRequestURL());\n                return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_GRANT);\n            }\n        } catch (final InvalidDPoPProofException e) {\n            LoggingUtils.error(LOGGER, e);\n            return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_DPOP_PROOF);\n        } catch (final Throwable e) {\n            LoggingUtils.error(LOGGER, e);\n            return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);\n        }\n\n        try {\n            val tokenRequestContext = examineAndExtractAccessTokenGrantRequest(request, response);\n            logProtocolRequest(tokenRequestContext);\n            LOGGER.debug(\"Creating access token for [{}]\", tokenRequestContext);\n            val generatedTokenResult = getConfigurationContext().getAccessTokenGenerator().generate(tokenRequestContext);\n            LOGGER.debug(\"Access token generated result is: [{}]\", generatedTokenResult);\n            return generateAccessTokenResponse(tokenRequestContext, generatedTokenResult);\n        } catch (final Throwable e) {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20AccessTokenEndpointController.java#L81-L117","documentation":"OAuth20AccessTokenEndpointController.handleRequest() calls verifyAccessTokenRequest() before issuing tokens; if any token-request validator fails (including DPoP-proof checks that surface as InvalidDPoPProofException), the controller logs this warning and writes an OAuth error response with error=invalid_grant (or invalid_dpop_proof for DPoP failures).","triggerScenarios":"A POST/GET to /oauth2.0/token fails validation: missing/invalid client credentials, unsupported or unauthorized grant_type, expired/unknown refresh token, bad redirect_uri/response_type, or an invalid DPoP proof header.","commonSituations":"Client misconfiguration after CAS upgrade; refresh token expired (in-memory registry lost it on restart); grant type not listed on the service definition; clock skew invalidating DPoP proofs; reverse proxy altering the request URL so verification fails.","solutions":["Read the accompanying warn logs (each validator logs its specific reason) to identify which check failed.","Fix the underlying validator condition: registered service grant/response types, token expiry settings, or client credentials.","For DPoP failures, regenerate the proof with correct HTTP method/URI and synchronized clock (check jti, iat skew).","Capture the full request (URL, parameters) and replay it with correct values; ensure proxies preserve the original URL and Authorization header."],"exampleFix":"// before\ncurl -d 'grant_type=refresh_token' https://cas/oauth2.0/token  # no token/credentials\n// after\ncurl -u clientId:secret -d 'grant_type=refresh_token&refresh_token=<valid-token>' https://cas/oauth2.0/token","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"const res = await fetch(tokenEndpoint, { method: 'POST', body: params });\nconst body = await res.json();\nif (body.error === 'invalid_grant') {\n  // inspect CAS warn logs / re-validate params before retrying\n  await reauthorize();\n} else if (body.error === 'invalid_dpop_proof') {\n  // rebuild DPoP proof with correct htm/htu/iat\n  retryWithNewDpopProof();\n}","preventionTips":["Tail CAS logs (validators log precise failure reasons) during client onboarding","Keep client clocks synced for DPoP/jwt validation","Ensure reverse proxies preserve request URL and Authorization headers","Maintain an end-to-end token-flow smoke test per client"],"tags":["oauth","token-endpoint","invalid-grant","request-validation"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}