spring-projects/spring-security · error · BadJwtException
Invalid alg / jwk parameter in JWS Header: alg=, jwk.kty=
Error message
Invalid alg / jwk parameter in JWS Header: alg=, jwk.kty=
What it means
The DPoP proof's jwk key type does not match the JWS algorithm, so no selector branch applies. The message includes the alg header and the jwk's kty to diagnose the mismatch (e.g. alg=RS256 with kty=EC, or an unsupported kty like oct).
Solutions
- Make the signing algorithm consistent with the embedded key type: RSA key → RS256/PS256, EC key → ES256 (or matching curve).
- Only embed an asymmetric public JWK matching the alg; symmetric (oct) keys and MAC algorithms are not valid for DPoP proofs.
- If verifying, reject the proof with invalid_token and surface the alg/kty values from this message to help the client debug.
Example fix
// before JWSSigner signer = new ECDSASigner(ecPrivateKey); signedWith(algorithm = JWSAlgorithm.RS256, jwk = ecPublicJwk); // mismatch // after signedWith(algorithm = JWSAlgorithm.ES256, jwk = ecPublicJwk);
Defensive patterns
Strategy: validation
Validate before calling
JWSAlgorithm alg = jwsHeader.getAlgorithm();
JWK jwk = jwsHeader.getJWK();
boolean ok = (JWSAlgorithm.Family.RSA.contains(alg) && jwk instanceof RSAKey)
|| (JWSAlgorithm.Family.EC.contains(alg) && jwk instanceof ECKey);
if (!ok) { /* fix alg/kty pairing before sending */ } Type guard
boolean algMatchesKey(JWSAlgorithm alg, JWK jwk) {
return (JWSAlgorithm.Family.RSA.contains(alg) && jwk instanceof RSAKey)
|| (JWSAlgorithm.Family.EC.contains(alg) && jwk instanceof ECKey);
} Try / catch
try { decoder.decode(proof); } catch (BadJwtException e) { /* reject: alg/kty mismatch, e.getMessage() has details */ } Prevention
- Pick the signer from the key, not vice versa: RSA key → RS256/PS256, EC key → ES256
- Never use MAC algorithms (HS256) or symmetric keys for DPoP proofs
- Add a smoke test that signs and verifies a proof locally before deploying
When it happens
Trigger: In DPoPProofJwtDecoderFactory.jwsKeySelector, the embedded jwk parsed fine but either (a) algorithm is RSA-family while jwk is not RSAKey, (b) algorithm is EC-family while jwk is not ECKey, or (c) algorithm is outside both families; final fallback throw before 'Invalid alg / jwk parameter in JWS Header: alg=' + ... is reached.
Common situations: Client signs with RS256 but embeds an EC key (or vice versa); proofs using oct/symmetric keys or algorithms like HS256, which are not allowed for DPoP; typos in alg header when hand-building proofs.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Failed to encode the JWT due to signing error: Failed to…
- Failed to encode the JWT due to signing error: Failed to…
- Invalid jwk parameter in JWS Header.
- Missing jwk parameter in JWS Header.
- Failed to encode the JWT due to signing error: Failed to…
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/189df5e6aa80afde.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofJwtDecoderFactory.java:206
throw new BadJwtException("Missing jwk parameter in JWS Header.");
}
if (jwk.isPrivate()) {
throw new BadJwtException("Invalid jwk parameter in JWS Header.");
}
try {
if (JWSAlgorithm.Family.RSA.contains(algorithm) && jwk instanceof RSAKey rsaKey) {
return Collections.singletonList(rsaKey.toRSAPublicKey());
}
else if (JWSAlgorithm.Family.EC.contains(algorithm) && jwk instanceof ECKey ecKey) {
return Collections.singletonList(ecKey.toECPublicKey());
}
}
catch (JOSEException ex) {
throw new BadJwtException("Invalid jwk parameter in JWS Header.");
}
throw new BadJwtException("Invalid alg / jwk parameter in JWS Header: alg=" + algorithm.getName()
+ ", jwk.kty=" + jwk.getKeyType().getValue());
};
}
private static final class AthClaimValidator implements OAuth2TokenValidator<Jwt> {
private final OAuth2Token accessToken;
private AthClaimValidator(OAuth2Token accessToken) {
Assert.notNull(accessToken, "accessToken cannot be null");
this.accessToken = accessToken;
}
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
Assert.notNull(jwt, "DPoP proof jwt cannot be null");
String accessTokenHashClaim = jwt.getClaimAsString("ath");
if (!StringUtils.hasText(accessTokenHashClaim)) {View on GitHub (pinned to 96852e8860)