apereo/cas · error

invalid_grant

invalid_grant

Error message

Access token validation failed for request [{}]

What it means

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).

Solutions

  1. Read the accompanying warn logs (each validator logs its specific reason) to identify which check failed.
  2. Fix the underlying validator condition: registered service grant/response types, token expiry settings, or client credentials.
  3. For DPoP failures, regenerate the proof with correct HTTP method/URI and synchronized clock (check jti, iat skew).
  4. Capture the full request (URL, parameters) and replay it with correct values; ensure proxies preserve the original URL and Authorization header.

Example fix

// before
curl -d 'grant_type=refresh_token' https://cas/oauth2.0/token  # no token/credentials
// after
curl -u clientId:secret -d 'grant_type=refresh_token&refresh_token=<valid-token>' https://cas/oauth2.0/token
Defensive patterns

Strategy: try-catch

Try / catch

const res = await fetch(tokenEndpoint, { method: 'POST', body: params });
const body = await res.json();
if (body.error === 'invalid_grant') {
  // inspect CAS warn logs / re-validate params before retrying
  await reauthorize();
} else if (body.error === 'invalid_dpop_proof') {
  // rebuild DPoP proof with correct htm/htu/iat
  retryWithNewDpopProof();
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/3419064c2ccfc32d. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20AccessTokenEndpointController.java:99

    /**
     * Handle request internal model and view.
     *
     * @param request  the request
     * @param response the response
     * @return the model and view
     * @throws Exception the exception
     */
    @PostMapping(path = {
        OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.ACCESS_TOKEN_URL,
        OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.TOKEN_URL},
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Operation(summary = "Handle OAuth access token request")
    public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        val context = new JEEContext(request, response);
        try {
            if (!verifyAccessTokenRequest(context)) {
                LOGGER.warn("Access token validation failed for request [{}]", context.getFullRequestURL());
                return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_GRANT);
            }
        } catch (final InvalidDPoPProofException e) {
            LoggingUtils.error(LOGGER, e);
            return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_DPOP_PROOF);
        } catch (final Throwable e) {
            LoggingUtils.error(LOGGER, e);
            return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);
        }

        try {
            val tokenRequestContext = examineAndExtractAccessTokenGrantRequest(request, response);
            logProtocolRequest(tokenRequestContext);
            LOGGER.debug("Creating access token for [{}]", tokenRequestContext);
            val generatedTokenResult = getConfigurationContext().getAccessTokenGenerator().generate(tokenRequestContext);
            LOGGER.debug("Access token generated result is: [{}]", generatedTokenResult);
            return generateAccessTokenResponse(tokenRequestContext, generatedTokenResult);
        } catch (final Throwable e) {

View on GitHub (pinned to e7288fc434)