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

  1. Make the signing algorithm consistent with the embedded key type: RSA key → RS256/PS256, EC key → ES256 (or matching curve).
  2. Only embed an asymmetric public JWK matching the alg; symmetric (oct) keys and MAC algorithms are not valid for DPoP proofs.
  3. 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

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


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)