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

  1. Convert the JWK to its public-only form before embedding: use toPublicJWK() on the RSAKey/ECKey when building the DPoP proof's jwk header.
  2. If you control proof generation, only serialize public parameters (kty, n, e for RSA; kty, crv, x, y for EC) in the jwk header.
  3. 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

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


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)