spring-projects/spring-security · error · BadJwtException

Missing jwk parameter in JWS Header.

Error message

Missing jwk parameter in JWS Header.

What it means

This BadJwtException is thrown by DPoPProofJwtDecoderFactory's jwsKeySelector when the DPoP proof JWT's JWS header does not embed the public JSON Web Key (jwk header parameter). RFC 9449 requires DPoP proofs to carry the signing public key in the header so the resource server can verify the signature without external key material.

Source

Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofJwtDecoderFactory.java:188

		jwtProcessor.setJWSTypeVerifier(DPOP_TYPE_VERIFIER);
		jwtProcessor.setJWSKeySelector(jwsKeySelector());
		// Override the default Nimbus claims set verifier and use jwtValidatorFactory for
		// 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()

View on GitHub (pinned to 96852e8860)

Solutions

  1. Set the jwk header when signing the proof: new JWSHeader.Builder(alg).jwk(publicJwk.toPublicJWK()).build().
  2. Ensure the embedded JWK is the public key (private keys are also rejected separately).
  3. Catch BadJwtException on decode and respond with an invalid_dpop_proof OAuth error.
  4. If using a DPoP client library, update/configure it so the public key is embedded per RFC 9449.

Example fix

// before
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build(); // no jwk
// after
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT)
        .jwk(rsaPublicJwk.toPublicJWK()).build();
Defensive patterns

Strategy: validation

Validate before calling

if (header.getJWK() == null) {
    throw new IllegalArgumentException("DPoP proof header must embed the public jwk");
}

Type guard

boolean embedsPublicJwk(JWSHeader header) {
    return header.getJWK() != null && !header.getJWK().isPrivate();
}

Try / catch

try {
    Jwt jwt = decoder.decode(proof);
} catch (BadJwtException ex) {
    if (ex.getMessage().contains("Missing jwk")) {
        logger.error("Client omitted jwk header in DPoP proof");
    }
}

Prevention

When it happens

Trigger: Decoding a DPoP proof JWT that was signed without the "jwk" header parameter set — e.g. a hand-built JWT via JWSHeader.Builder without jwk(jwk), or a client library that doesn't embed the key.

Common situations: Custom JWT code that omits the jwk header (normal JWTs embed keys elsewhere), a client migration where the DPoP library changed, or a proof generated by another tool that references the key by kid instead of embedding it.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/c20dc7110878c2fe. Report an issue: GitHub.