apereo/cas · warning

missing_access_token

missing_access_token

Error message

Access token cannot be found in the request

What it means

The introspection request passed client authentication checks but did not include the access token to introspect. CAS requires either a 'token' or 'access_token' request parameter; when neither is present it returns a 400 with error 'missing_access_token' (OAuth 2.0 Token Introspection RFC 7662 requires the token parameter).

Solutions

  1. Include the token as a form parameter: token=<access-token> in the introspection POST body
  2. Alternatively use the access_token parameter name
  3. Send the request as application/x-www-form-urlencoded so parameters are parsed
  4. Do not rely on the Authorization: Bearer header to carry the token to introspect

Example fix

// before
curl -u myClient:mySecret -X POST https://cas.example.org/cas/oauth2.0/introspect
// after
curl -u myClient:mySecret -X POST https://cas.example.org/cas/oauth2.0/introspect -d 'token=AT-xxxx'
Defensive patterns

Strategy: validation

Validate before calling

function validateIntrospectionParams(params) {
  if (!params.token && !params.access_token) {
    throw new Error('introspection requires a token or access_token parameter');
  }
  const body = new URLSearchParams(params);
  return body;
}

Type guard

function hasToken(params) {
  return params instanceof URLSearchParams && (params.has('token') || params.has('access_token'));
}

Try / catch

try {
  await introspect(params);
} catch (e) {
  if (e.response?.status === 400 && e.response.data?.error === 'missing_access_token') {
    // add token parameter and retry once
  }
}

Prevention

When it happens

Trigger: POSTing to /oauth2.0/introspect with valid client credentials but no 'token' (or 'access_token') form parameter, or sending the token in a header/JSON body where CAS does not look for it.

Common situations: Client SDK sends token under a custom field name; token put in the Authorization: Bearer header instead of a form parameter; truncation of the request body; developer assuming GET-style query params are checked when doing a POST without form encoding.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/d1256843bded1547. 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/OAuth20IntrospectionEndpointController.java:168

        return null;
    }

    protected Optional<Credentials> extractCredentials(final JEEContext context) {
        val authExtractor = new BasicAuthExtractor();
        val callContext = new CallContext(context, getConfigurationContext().getSessionStore(),
            getConfigurationContext().getOauthConfig().getProfileManagerFactory());
        return authExtractor.extract(callContext);
    }


    private Optional<ResponseEntity<? extends @NonNull BaseOAuth20IntrospectionAccessTokenResponse>> validateIntrospectionRequest(
        final OAuthRegisteredService registeredService, final UsernamePasswordCredentials credentials,
        final HttpServletRequest request) throws Throwable {
        val tokenExists = HttpRequestUtils.doesParameterExist(request, OAuth20Constants.TOKEN)
            || HttpRequestUtils.doesParameterExist(request, OAuth20Constants.ACCESS_TOKEN);

        if (!tokenExists) {
            LOGGER.warn("Access token cannot be found in the request");
            return Optional.of(buildBadRequestResponseEntity(OAuth20Constants.MISSING_ACCESS_TOKEN));
        }

        if (getConfigurationContext().getClientSecretValidator().validate(registeredService, credentials.getPassword())) {
            val service = getConfigurationContext().getWebApplicationServiceServiceFactory().createService(registeredService.getServiceId());
            val audit = AuditableContext.builder()
                .service(service)
                .registeredService(registeredService)
                .build();
            val accessResult = getConfigurationContext().getRegisteredServiceAccessStrategyEnforcer().execute(audit);
            return accessResult.isExecutionFailure()
                ? Optional.of(buildUnauthorizedResponseEntity(OAuth20Constants.UNAUTHORIZED_CLIENT, false))
                : Optional.empty();
        }
        LOGGER.warn("Unable to match client secret for registered service [{}] with client id [{}]",
            registeredService.getName(), registeredService.getClientId());
        return Optional.of(buildUnauthorizedResponseEntity(OAuth20Constants.INVALID_CLIENT, true));
    }

View on GitHub (pinned to e7288fc434)