apereo/cas · error
The access token is invalid, expired, has an invalid grant…
Error message
The access token is invalid, expired, has an invalid grant type or no authorization details.
What it means
verifyRequest fetches the bearer access token from the request and looks it up in the ticket registry as an OAuth20AccessToken; validateAccessToken then checks validity, expiration, grant type, and presence of authorization details. If any check fails, the controller returns HTTP 400 with an 'Invalid access token' error response.
Solutions
- Obtain a fresh access token via a supported grant and retry before it expires.
- Verify the token is an OAuth access token issued by this CAS instance (same ticket registry across nodes).
- Request the required authorization_details scopes/details during authorization so the token qualifies for VC issuance.
- Confirm the grant type used is allowed for verifiable credential issuance.
Example fix
// before curl -H "Authorization: Bearer expired-token" https://sso.example.org/cas/oidc/vc/credential // after # fetch a new token, then: curl -H "Authorization: Bearer $NEW_ACCESS_TOKEN" https://sso.example.org/cas/oidc/vc/credential
Defensive patterns
Strategy: retry
Validate before calling
// Optionally introspect the token before use
if (accessToken == null || accessToken.isBlank()) {
throw new IllegalStateException("Access token required for VC credential endpoint");
} Try / catch
// On 'Invalid access token', re-authenticate once
Response resp = client.send(vcRequest);
if (resp.status() == 400 && body.contains("Invalid access token")) {
accessToken = fetchNewAccessToken();
resp = client.send(vcRequest);
} Prevention
- Refresh access tokens proactively before expiry
- Send the access token (not the ID token) as Bearer credential
- Ensure all CAS nodes share the same ticket registry
- Request required authorization_details/scopes during token acquisition
When it happens
Trigger: POSTing to the VC credential endpoint with a missing, expired, revoked, or mistyped bearer token; the token was issued with a grant type not permitted for VC issuance; the token lacks authorization_details required for verifiable credentials.
Common situations: Client cached an expired token; using an ID token instead of an access token; token obtained from a different CAS node/registry (shared registry misconfiguration); token issued before enabling VC authorization details.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid access token
- Missing required principal attribute for claim
- Proof nonce is invalid or missing
- Proof JWT signature validation failed
- Proof audience does not match credential issuer
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/5d16def6ba5547ed.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/web/OidcVerifiableCredentialEndpointController.java:173
return responses.size() == 1
? ResponseEntity.ok(responses.getFirst())
: ResponseEntity.ok(Map.of("credential_responses", responses));
}
protected Couplet<@Nullable OAuth20AccessToken, @Nullable ResponseEntity> verifyRequest(
final HttpServletRequest httpRequest,
final HttpServletResponse httpResponse) {
val webContext = new JEEContext(httpRequest, httpResponse);
if (!getConfigurationContext().getIssuerService().validateIssuer(webContext, List.of(OidcConstants.VC_CREDENTIAL_URL))) {
LOGGER.warn("CAS cannot accept the request given the issuer is invalid.");
val body = OAuth20Utils.getErrorResponseBody(OAuth20Constants.INVALID_REQUEST, "Invalid issuer");
return Couplet.right(ResponseEntity.badRequest().body(body));
}
val decodedAccessTokenId = getAccessTokenFromRequest(httpRequest).getValue();
val decodedToken = getConfigurationContext().getTicketRegistry().getTicket(decodedAccessTokenId, OAuth20AccessToken.class);
if (!validateAccessToken(decodedToken)) {
LOGGER.warn("The access token is invalid, expired, has an invalid grant type or no authorization details.");
return Couplet.right(ResponseEntity.badRequest()
.body(OAuth20Utils.getErrorResponseBody(OAuth20Constants.ERROR, "Invalid access token")));
}
return Couplet.left(decodedToken);
}
protected boolean validateAccessToken(@Nullable final OAuth20AccessToken accessToken) {
return accessToken != null && !accessToken.isExpired()
&& (accessToken.getGrantType() == OAuth20GrantTypes.PRE_AUTHORIZED_CODE || accessToken.hasAuthorizationDetails());
}
/**
* Handle errors.
*
* @param ex the ex
* @return the response entity
*/
@ExceptionHandler(Exception.class)View on GitHub (pinned to e7288fc434)