{"record":{"id":"980428bb1d911c1b","repo":"spring-projects/spring-security","slug":"invalid-jwk-parameter-in-jws-header","errorCode":null,"errorMessage":"Invalid jwk parameter in JWS Header.","messagePattern":"Invalid jwk parameter in JWS Header\\.","errorType":"exception","errorClass":"BadJwtException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofJwtDecoderFactory.java","lineNumber":191,"sourceCode":"\t\t// claims validation\n\t\tjwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {\n\t\t});\n\t\treturn new NimbusJwtDecoder(jwtProcessor);\n\t}\n\n\tprivate static JWSKeySelector<SecurityContext> jwsKeySelector() {\n\t\treturn (header, context) -> {\n\t\t\tJWSAlgorithm algorithm = header.getAlgorithm();\n\t\t\tif (!JWSAlgorithm.Family.RSA.contains(algorithm) && !JWSAlgorithm.Family.EC.contains(algorithm)) {\n\t\t\t\tthrow new BadJwtException(\"Unsupported alg parameter in JWS Header: \" + algorithm.getName());\n\t\t\t}\n\n\t\t\tJWK jwk = header.getJWK();\n\t\t\tif (jwk == null) {\n\t\t\t\tthrow new BadJwtException(\"Missing jwk parameter in JWS Header.\");\n\t\t\t}\n\t\t\tif (jwk.isPrivate()) {\n\t\t\t\tthrow new BadJwtException(\"Invalid jwk parameter in JWS Header.\");\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tif (JWSAlgorithm.Family.RSA.contains(algorithm) && jwk instanceof RSAKey rsaKey) {\n\t\t\t\t\treturn Collections.singletonList(rsaKey.toRSAPublicKey());\n\t\t\t\t}\n\t\t\t\telse if (JWSAlgorithm.Family.EC.contains(algorithm) && jwk instanceof ECKey ecKey) {\n\t\t\t\t\treturn Collections.singletonList(ecKey.toECPublicKey());\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (JOSEException ex) {\n\t\t\t\tthrow new BadJwtException(\"Invalid jwk parameter in JWS Header.\");\n\t\t\t}\n\n\t\t\tthrow new BadJwtException(\"Invalid alg / jwk parameter in JWS Header: alg=\" + algorithm.getName()\n\t\t\t\t\t+ \", jwk.kty=\" + jwk.getKeyType().getValue());\n\t\t};\n\t}","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofJwtDecoderFactory.java#L173-L209","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nRSAKey fullKey = new RSAKey.Builder(publicKey).privateKey(privateKey).build();\nheaderBuilder.jwk(fullKey);\n// after\nRSAKey publicOnly = (RSAKey) new RSAKey.Builder(publicKey).privateKey(privateKey).build().toPublicJWK();\nheaderBuilder.jwk(publicOnly);","handlingStrategy":"validation","validationCode":"JWK jwk = /* jwk destined for the proof header */;\nif (jwk.isPrivate()) {\n    jwk = jwk.toPublicJWK(); // or reject\n}","typeGuard":"boolean isSafePublicJwk(JWK jwk) { return jwk != null && !jwk.isPrivate(); }","tryCatchPattern":"try { decoder.decode(proof); } catch (BadJwtException e) { /* reject proof: embedded jwk must be public */ }","preventionTips":["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"],"tags":["jwk","dpop","jwt","security"],"backgroundTag":"invalid-argument-value","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}