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
- 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.
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
- 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
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
- No client id is provided in the request
- Subject token type is not supported
- Actor token type is not supported
- Cannot save a resource set with inconsistent scopes.
- Cannot update a resource set without identifiers.
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)