apereo/cas · error · ResponseStatusException

Invalid access token

Error message

Invalid access token

What it means

The JWKS registration endpoint requires a bearer access token that grants the scope client_jwks_registration and is still valid. The controller looks the token up in the ticket registry and rejects the request with HTTP 401 (ResponseStatusException) when the token is missing, expired, or lacks the required scope.

Solutions

  1. Obtain a fresh access token that includes the client_jwks_registration scope (check the client's granted scopes in its registered service definition)
  2. Send the token as a proper Bearer Authorization header on the JWKS registration request
  3. Verify the token has not expired; request a new one if needed and confirm server/client clock sync

Example fix

// before
curl -X POST .../oidc/jwks -d 'jwks=...'  // no/insufficient token
// after
curl -X POST .../oidc/jwks -H "Authorization: Bearer <token_with_client_jwks_registration_scope>" -d 'jwks=...'
Defensive patterns

Strategy: try-catch

Validate before calling

OAuth20AccessToken token = ticketRegistry.getTicket(accessToken, OAuth20AccessToken.class);
if (token == null || token.isExpired()
        || !token.getScopes().contains(OidcConstants.CLIENT_JWKS_REGISTRATION_SCOPE)) {
    throw new IllegalStateException("access token lacks client_jwks_registration scope");
}

Try / catch

try { controller.handleRegistration(request, response); }
catch (ResponseStatusException e) {
    if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { /* obtain a new scoped token and retry once */ }
    else throw e;
}

Prevention

When it happens

Trigger: POSTing to the JWKS registration endpoint with an access token that (a) is not found in the ticket registry, (b) is expired, or (c) does not contain the OidcConstants.CLIENT_JWKS_REGISTRATION_SCOPE scope.

Common situations: Using the client's regular API access token without the jwks registration scope; reusing a token after it expired; omitting or mangling the Authorization header so the token lookup returns null; clock skew causing premature expiry.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/web/controllers/jwks/OidcJwksRegistrationEndpointController.java:88

        description = "This endpoint allows clients to register their JSON Web Keys (JWKs) for use in OpenID Connect operations",
        requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
            required = true,
            content = @Content(
                schema = @Schema(implementation = ClientJwksRegistrationRequest.class)
            )
        ))
    public ResponseEntity handleRegistration(
        final HttpServletRequest request,
        @RequestBody final ClientJwksRegistrationRequest registrationRequest) throws Throwable {
        val accessToken = getAccessTokenFromRequest(request).getValue();
        val accessTokenTicket = FunctionUtils.doAndHandle(() -> {
            val decodedToken = getConfigurationContext().getTicketRegistry().getTicket(accessToken, OAuth20AccessToken.class);
            return decodedToken == null || decodedToken.isExpired() ? null : decodedToken;
        });
        if (accessTokenTicket == null
            || accessTokenTicket.isExpired()
            || !accessTokenTicket.getScopes().contains(OidcConstants.CLIENT_JWKS_REGISTRATION_SCOPE)) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid access token");
        }
        val clientId = accessTokenTicket.getClientId();
        val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(
            configurationContext.getServicesManager(), clientId, OidcRegisteredService.class);
        val audit = AuditableContext.builder()
            .registeredService(registeredService)
            .authentication(accessTokenTicket.getAuthentication())
            .build();
        val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit);
        accessResult.throwExceptionIfNeeded();
        
        val jws = JWSObject.parse(registrationRequest.proof());

        val alg = jws.getHeader().getAlgorithm();
        FunctionUtils.throwIf(!JWSAlgorithm.Family.EC.contains(alg) && !JWSAlgorithm.Family.RSA.contains(alg) && !JWSAlgorithm.EdDSA.equals(alg),
            () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid algorithm: " + alg));

        val jwk = jws.getHeader().getJWK();

View on GitHub (pinned to e7288fc434)