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
- Obtain a fresh access token that includes the client_jwks_registration scope (check the client's granted scopes in its registered service definition)
- Send the token as a proper Bearer Authorization header on the JWKS registration request
- 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
- Request the client_jwks_registration scope when obtaining the token
- Refresh tokens before they expire for JWKS registration calls
- Confirm the registered service grants the required scope
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
- Invalid signature
- JWKS cannot contain expressions
- Service with client id is configured to encrypt tokens, yet…
- Unsupported key type:
- Unable to locate JSON web key for
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)