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

  1. Obtain a fresh access token via a supported grant and retry before it expires.
  2. Verify the token is an OAuth access token issued by this CAS instance (same ticket registry across nodes).
  3. Request the required authorization_details scopes/details during authorization so the token qualifies for VC issuance.
  4. 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

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

Related errors


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)