spring-projects/spring-security · error · BadJwtException
Invalid jwk parameter in JWS Header.
Error message
Invalid jwk parameter in JWS Header.
What it means
NimbusJwtDecoder's DPoP key selector rejects a DPoP proof whose embedded `jwk` header contains a PRIVATE key. A proof of possession token must carry only the public key so verifiers can check the signature; embedding the private key is insecure and invalid per RFC 9449.
Solutions
- Convert the JWK to its public-only form before embedding: use toPublicJWK() on the RSAKey/ECKey when building the DPoP proof's jwk header.
- If you control proof generation, only serialize public parameters (kty, n, e for RSA; kty, crv, x, y for EC) in the jwk header.
- If you are only verifying third-party proofs, treat this token as a malformed/misbehaving client proof and reject it with invalid_token / DPoP error.
Example fix
// before RSAKey fullKey = new RSAKey.Builder(publicKey).privateKey(privateKey).build(); headerBuilder.jwk(fullKey); // after RSAKey publicOnly = (RSAKey) new RSAKey.Builder(publicKey).privateKey(privateKey).build().toPublicJWK(); headerBuilder.jwk(publicOnly);
Defensive patterns
Strategy: validation
Validate before calling
JWK jwk = /* jwk destined for the proof header */;
if (jwk.isPrivate()) {
jwk = jwk.toPublicJWK(); // or reject
} Type guard
boolean isSafePublicJwk(JWK jwk) { return jwk != null && !jwk.isPrivate(); } Try / catch
try { decoder.decode(proof); } catch (BadJwtException e) { /* reject proof: embedded jwk must be public */ } Prevention
- Always call toPublicJWK() before embedding a JWK in a DPoP proof header
- Never include private key material ('d', private exponent) in any transmitted header
- Unit-test proof generation by asserting the header jwk has no private params
When it happens
Trigger: DPoPProofJwtDecoderFactory.buildDecoder's jwsKeySelector encounters a JWS header whose jwk JWK object returns true for isPrivate() (e.g. it contains 'd' for EC/RSA or other private parameters).
Common situations: Client code passes the full generated JWK object (from a Nimbus JWTSAVER or its own key generation) into the proof header instead of converting it to a public JWK first; tests hand-crafting proofs with RSAKey.Builder that includes the private exponent.
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
- Invalid alg / jwk parameter in JWS Header: alg=, jwk.kty=
- Missing jwk parameter in JWS Header.
- An error occurred while attempting to decode the Jwt…
- Failed to encode the JWT due to signing error: Failed to…
- 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/980428bb1d911c1b.
Report an issue: GitHub.
Appendix: source
Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofJwtDecoderFactory.java:191
// claims validation
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
});
return new NimbusJwtDecoder(jwtProcessor);
}
private static JWSKeySelector<SecurityContext> jwsKeySelector() {
return (header, context) -> {
JWSAlgorithm algorithm = header.getAlgorithm();
if (!JWSAlgorithm.Family.RSA.contains(algorithm) && !JWSAlgorithm.Family.EC.contains(algorithm)) {
throw new BadJwtException("Unsupported alg parameter in JWS Header: " + algorithm.getName());
}
JWK jwk = header.getJWK();
if (jwk == null) {
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());
};
}View on GitHub (pinned to 96852e8860)