apereo/cas · error · CredentialsException

Missing scope [ ]. Unable to authenticate access token

Error message

Missing scope [%s]. Unable to authenticate access token %s

What it means

BaseUmaTokenAuthenticator.validate extracts the UMA access token from the request, looks it up in the ticket registry as an OAuth20AccessToken, and requires that the token carries the configured required UMA scope (e.g. uma_protection). If the token's scopes do not include it, a CredentialsException is thrown and the request is not authenticated. This enforces that only protection-scoped tokens may call UMA protection endpoints.

Solutions

  1. Request the required scope (default 'uma_protection') in the OAuth2 authorization/token request.
  2. Check which scope BaseUmaTokenAuthenticator is configured with and add it to the client's allowed scopes.
  3. Issue a new access token after fixing the scope request; existing tokens cannot be amended.
  4. Verify the service definition's supported scopes/evaluator are not stripping the scope at grant time.

Example fix

// before
github-like client: GET /oauth2.0/authorize?client_id=c&response_type=code&scope=read
// after
GET /oauth2.0/authorize?client_id=c&response_type=code&scope=read%20uma_protection
Defensive patterns

Strategy: try-catch

Validate before calling

OAuth20AccessToken at = ticketRegistry.getTicket(token, OAuth20AccessToken.class);
if (at == null || !at.getScopes().contains("uma_protection")) {
    throw new CredentialsException("token lacks uma_protection scope");
}

Try / catch

try { authenticator.validate(callContext, creds); } catch (CredentialsException e) {
    return Optional.empty(); // results in 401 with WWW-Authenticate
}

Prevention

When it happens

Trigger: Presenting an OAuth2 access token to a UMA endpoint whose grant did not request/include the required scope (e.g. token issued without 'uma_protection'), typically at the permission or resource-set registration endpoints.

Common situations: Clients requesting tokens without the uma_protection scope in the authorization request; CAS service definition narrowing the allowed scopes; using a regular API token instead of one minted for UMA protection.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/9cd1e916bce00270. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oauth-uma-core/src/main/java/org/apereo/cas/uma/web/authn/BaseUmaTokenAuthenticator.java:40

 *
 * @author Misagh Moayyed
 * @since 6.0.0
 */
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Slf4j
public abstract class BaseUmaTokenAuthenticator implements Authenticator {
    private final TicketRegistry ticketRegistry;

    private final JwtBuilder accessTokenJwtBuilder;

    @Override
    public Optional<Credentials> validate(final CallContext callContext, final Credentials creds) {
        val credentials = (TokenCredentials) creds;
        val token = extractAccessTokenFrom(credentials.getToken().trim());
        val at = ticketRegistry.getTicket(token, OAuth20AccessToken.class);
        if (!at.getScopes().contains(getRequiredScope())) {
            val err = String.format("Missing scope [%s]. Unable to authenticate access token %s", getRequiredScope(), token);
            throw new CredentialsException(err);
        }
        val profile = new CommonProfile();
        val authentication = at.getAuthentication();
        val principal = authentication.getPrincipal();
        profile.setId(principal.getId());
        val attributes = new LinkedHashMap<String, Object>(authentication.getAttributes());
        attributes.putAll(principal.getAttributes());

        profile.addAttributes(attributes);
        profile.addRoles(at.getScopes());
        profile.addAttribute(OAuth20AccessToken.class.getName(), at);
        profile.addAttribute(OAuth20Constants.CLIENT_ID, at.getClientId());

        LOGGER.debug("Authenticated access token [{}]", profile);
        credentials.setUserProfile(profile);
        return Optional.of(credentials);
    }

View on GitHub (pinned to e7288fc434)