jwtk/jjwt · error · UnsupportedJwtException
Cannot verify JWS signature: unable to locate signature veri
Error message
Cannot verify JWS signature: unable to locate signature verification key for JWS with header: ${jwsHeader} What it means
verifySignature() asks the configured SigningKeyResolver (or parser key configuration) for the verification key for the JWS header/payload/claims. If the resolver returns null, the parser cannot verify the signature and throws UnsupportedJwtException with this message.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:293
try {
algorithm = (SecureDigestAlgorithm<?, Key>) sigAlgs.apply(jwsHeader);
} catch (UnsupportedJwtException e) {
//For backwards compatibility. TODO: remove this try/catch block for 1.0 and let UnsupportedJwtException propagate
String msg = "Unsupported signature algorithm '" + alg + "': " + e.getMessage();
throw new SignatureException(msg, e);
}
Assert.stateNotNull(algorithm, "JWS Signature Algorithm cannot be null.");
//digitally signed, let's assert the signature:
Key key;
if (claims != null) {
key = resolver.resolveSigningKey(jwsHeader, claims);
} else {
key = resolver.resolveSigningKey(jwsHeader, payload.getBytes());
}
if (key == null) {
String msg = "Cannot verify JWS signature: unable to locate signature verification key for JWS with header: " + jwsHeader;
throw new UnsupportedJwtException(msg);
}
Provider provider = ProviderKey.getProvider(key, this.provider); // extract if necessary
key = ProviderKey.getKey(key); // unwrap if necessary, MUST be called after ProviderKey.getProvider
Assert.stateNotNull(key, "ProviderKey cannot be null."); //ProviderKey impl doesn't allow null
if (key instanceof PrivateKey) {
throw new InvalidKeyException(PRIV_KEY_VERIFY_MSG);
}
final byte[] signature = decode(tokenized.getDigest(), "JWS signature");
//re-create the jwt part without the signature. This is what is needed for signature verification:
InputStream payloadStream = null;
InputStream verificationInput;
if (jwsHeader.isPayloadEncoded()) {
int len = tokenized.getProtected().length() + 1 + tokenized.getPayload().length();
CharBuffer cb = CharBuffer.allocate(len);
cb.put(Strings.wrap(tokenized.getProtected()));
cb.put(SEPARATOR_CHAR);View on GitHub (pinned to fb71496164)
Solutions
- Fix the SigningKeyResolver/keyLocator to return the correct key for the header (match on kid/issuer) or throw a descriptive exception instead of returning null
- Ensure the token's kid exists in your JWKS/key store — refresh keys on rotation
- Verify the resolver handles all expected key types/headers before returning
Example fix
// before
public Key resolveSigningKey(JwsHeader h, Claims c) {
return keys.get(h.getKeyId()); // may be null
}
// after
public Key resolveSigningKey(JwsHeader h, Claims c) {
Key k = keys.get(h.getKeyId());
if (k == null) throw new SignatureException("Unknown kid: " + h.getKeyId());
return k;
} Defensive patterns
Strategy: validation
Validate before calling
Key key = keys.get(header.getKeyId());
if (key == null) {
throw new SignatureException("No verification key for kid: " + header.getKeyId());
} Try / catch
try {
return parser.build().parseSignedClaims(token);
} catch (UnsupportedJwtException e) {
if (e.getMessage().contains("unable to locate signature verification key")) {
keyProvider.refresh(); // JWKS rotation
}
throw new UnauthorizedException(e);
} Prevention
- Never return null from SigningKeyResolver — throw instead
- Refresh JWKS on unknown kid before rejecting
- Cover all key types in resolver branching logic
When it happens
Trigger: Using .keyLocator(...) or a custom SigningKeyResolver whose resolveSigningKey(header, claims/payload) returns null (e.g. unmatched kid in a JWKS lookup, unsupported key type in the resolver's switch).
Common situations: Multi-tenant JWKS key location where the token's 'kid' is not in the key set yet (rotation lag); resolver logic that returns null instead of throwing for unknown kids; parser configured without any key at all for resolver-based flows.
Related errors
- Unsupported signature algorithm '${alg}': ${e.getMessage()}
- PrivateKeys may not be used to verify digital signatures. Pr
- Unable to verify JWS signature: the parser has encountered a
- Invalid ECDSA signature format.
- The 'none' algorithm cannot be used to create signatures.
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/8e0e794a7396de43.
Report an issue: GitHub.