{"record":{"id":"5d16def6ba5547ed","repo":"apereo/cas","slug":"the-access-token-is-invalid-expired-has-an-inval","errorCode":null,"errorMessage":"The access token is invalid, expired, has an invalid grant type or no authorization details.","messagePattern":"The access token is invalid, expired, has an invalid grant type or no authorization details\\.","errorType":"console","errorClass":null,"httpStatus":400,"severity":"error","filePath":"support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/web/OidcVerifiableCredentialEndpointController.java","lineNumber":173,"sourceCode":"        return responses.size() == 1\n            ? ResponseEntity.ok(responses.getFirst())\n            : ResponseEntity.ok(Map.of(\"credential_responses\", responses));\n    }\n\n    protected Couplet<@Nullable OAuth20AccessToken, @Nullable ResponseEntity> verifyRequest(\n        final HttpServletRequest httpRequest,\n        final HttpServletResponse httpResponse) {\n        val webContext = new JEEContext(httpRequest, httpResponse);\n        if (!getConfigurationContext().getIssuerService().validateIssuer(webContext, List.of(OidcConstants.VC_CREDENTIAL_URL))) {\n            LOGGER.warn(\"CAS cannot accept the request given the issuer is invalid.\");\n            val body = OAuth20Utils.getErrorResponseBody(OAuth20Constants.INVALID_REQUEST, \"Invalid issuer\");\n            return Couplet.right(ResponseEntity.badRequest().body(body));\n        }\n\n        val decodedAccessTokenId = getAccessTokenFromRequest(httpRequest).getValue();\n        val decodedToken = getConfigurationContext().getTicketRegistry().getTicket(decodedAccessTokenId, OAuth20AccessToken.class);\n        if (!validateAccessToken(decodedToken)) {\n            LOGGER.warn(\"The access token is invalid, expired, has an invalid grant type or no authorization details.\");\n            return Couplet.right(ResponseEntity.badRequest()\n                .body(OAuth20Utils.getErrorResponseBody(OAuth20Constants.ERROR, \"Invalid access token\")));\n        }\n        return Couplet.left(decodedToken);\n    }\n\n    protected boolean validateAccessToken(@Nullable final OAuth20AccessToken accessToken) {\n        return accessToken != null && !accessToken.isExpired()\n            && (accessToken.getGrantType() == OAuth20GrantTypes.PRE_AUTHORIZED_CODE || accessToken.hasAuthorizationDetails());\n    }\n\n    /**\n     * Handle errors.\n     *\n     * @param ex the ex\n     * @return the response entity\n     */\n    @ExceptionHandler(Exception.class)","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/web/OidcVerifiableCredentialEndpointController.java#L155-L191","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncurl -H \"Authorization: Bearer expired-token\" https://sso.example.org/cas/oidc/vc/credential\n// after\n# fetch a new token, then:\ncurl -H \"Authorization: Bearer $NEW_ACCESS_TOKEN\" https://sso.example.org/cas/oidc/vc/credential","handlingStrategy":"retry","validationCode":"// Optionally introspect the token before use\nif (accessToken == null || accessToken.isBlank()) {\n    throw new IllegalStateException(\"Access token required for VC credential endpoint\");\n}","typeGuard":null,"tryCatchPattern":"// On 'Invalid access token', re-authenticate once\nResponse resp = client.send(vcRequest);\nif (resp.status() == 400 && body.contains(\"Invalid access token\")) {\n    accessToken = fetchNewAccessToken();\n    resp = client.send(vcRequest);\n}","preventionTips":["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"],"tags":["oidc","oauth2","access-token","verifiable-credentials"],"backgroundTag":"authentication-required","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"}