apereo/cas · error · ResponseStatusException
Invalid signature
Error message
Invalid signature
What it means
After selecting the appropriate verifier for the embedded JWK, the controller verifies the JWS signature of the client JWKS registration request. If verification fails, the request is rejected with HTTP 401 'Invalid signature', since the request cannot be attributed to the holder of the registered key.
Solutions
- Re-sign the registration JWT with the private key matching the embedded public JWK and sign again after finalizing the payload
- Confirm the JWS algorithm in the header matches the key type and the signer used
- Regenerate the keypair and rebuild the signed request from scratch to rule out payload tampering/encoding issues
Example fix
// before: signing with mismatched key JWSSigner signer = new RSASSASigner(otherRsaKey); // after: sign with private key of the embedded JWK JWSSigner signer = new RSASSASigner(embeddedRsaJwk.toRSAPrivateKey());
Defensive patterns
Strategy: validation
Validate before calling
JWSSigner signer = ...; // key matching the embedded JWK
SignedJWT jws = new SignedJWT(header, claims);
jws.sign(signer);
if (!jws.verify(verifierForEmbeddedJwk)) {
throw new IllegalStateException("JWS does not verify against embedded JWK");
} Try / catch
try { controller.handleRegistration(request, response); }
catch (ResponseStatusException e) {
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED && "Invalid signature".equals(e.getReason())) { /* re-sign with the correct private key */ }
else throw e;
} Prevention
- Always sign with the private key corresponding to the embedded public JWK
- Sign only after the payload is final; never mutate claims post-signing
- Add a local verify() step before sending the request
When it happens
Trigger: handleRegistration(): jws.verify(verifier) returns false — the JWT was signed by a different key than the one embedded/claimed, the payload was modified after signing, or the wrong algorithm/key material was used by the client.
Common situations: Signing with the private key of a different keypair than the embedded public JWK; serializing the JWT in a way that alters the payload after signing; mismatched algorithm between header and actual signature; key rotation on the client mid-request.
Related errors
- Invalid access token
- Unsupported key type:
- Unable to verify JWT assertion with any of the configured…
- JWKS cannot contain expressions
- Service with client id is configured to encrypt tokens, yet…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/4ce0b03499610420.
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:114
.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();
val verifier = switch (jwk) {
case ECKey ecKey -> new ECDSAVerifier(ecKey);
case RSAKey rsaKey -> new RSASSAVerifier(rsaKey);
case OctetKeyPair okp -> new Ed25519Verifier(okp.toPublicJWK());
default -> throw new IllegalArgumentException("Unsupported key type: " + jwk.getKeyType());
};
if (!jws.verify((JWSVerifier) verifier)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid signature");
}
val jkt = jwk.computeThumbprint().toString();
clientJwksRegistrationStore.save(accessTokenTicket.getClientId(), jkt, jwk.toPublicJWK().toJSONString());
return ResponseEntity.ok(new ClientJwksRegistrationResponse(jkt));
}
/**
* Handle errors.
*
* @param ex the ex
* @return the response entity
*/
@ExceptionHandler(Exception.class)
@SuppressWarnings("UnusedMethod")
private static ResponseEntity<String> handle(final Exception ex) {
LoggingUtils.error(LOGGER, ex);
if (ex instanceof final ResponseStatusException rse) {
return ResponseEntity.status(rse.getStatusCode()).body(rse.getReason());View on GitHub (pinned to e7288fc434)