spring-projects/spring-security · error · BadJwtException

Unsupported alg parameter in JWS Header: ${algorithm.getName

Error message

Unsupported alg parameter in JWS Header: ${algorithm.getName()}

What it means

This BadJwtException is thrown by DPoPProofJwtDecoderFactory's jwsKeySelector when the DPoP proof JWT's JWS header declares an algorithm outside the RSA or EC families (e.g. HS256 or none). The factory only accepts asymmetric RSA/EC algorithms, per RFC 9449, and rejects everything else before key selection.

Source

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

		return delegatingTokenValidator;
	}

	private static NimbusJwtDecoder buildDecoder() {
		ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
		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());
				}
			}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Use RS256/RS384/RS512, ES256/ES384/ES512, or PS* algorithms when creating the DPoP proof (e.g. RSA JWS signer or ECDSASigner in Nimbus).
  2. Catch BadJwtException on decode and return a 401 invalid_dpop_proof error.
  3. If you control the client config, ensure its DPoP signer matches RSA or EC keys.
  4. Verify the proof header is produced by a DPoP library, not hand-rolled JWT code defaulting to HS256.

Example fix

// before
JWSSigner signer = new MACSigner(secret); // produces HS256 -> rejected
// after
JWSSigner signer = new RSASSASigner(rsaKey); // RS256, accepted
Defensive patterns

Strategy: validation

Validate before calling

Set<JWSAlgorithm> allowed = new HashSet<>();
allowed.addAll(JWSAlgorithm.Family.RSA);
allowed.addAll(JWSAlgorithm.Family.EC);
if (!allowed.contains(header.getAlgorithm())) { throw new IllegalArgumentException("alg not allowed"); }

Type guard

boolean isRsaOrEc(JWSAlgorithm alg) {
    return JWSAlgorithm.Family.RSA.contains(alg) || JWSAlgorithm.Family.EC.contains(alg);
}

Try / catch

try {
    Jwt jwt = decoder.decode(proof);
} catch (BadJwtException ex) {
    throw new OAuth2AuthorizationCodeException(...); // respond 401 invalid_dpop_proof
}

Prevention

When it happens

Trigger: Decoding a DPoP proof JWT whose JOSE header alg is not in JWSAlgorithm.Family.RSA or Family.EC (e.g. HS256, PS-misconfigured variants are fine, but HS256/others throw).

Common situations: A client signs the proof with HS256 (symmetric), uses 'none', or an older client library defaults to a non-RSA/EC algorithm; or a malicious/misrouted JWT is fed to the DPoP decoder.

Related errors


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