{"record":{"id":"f2573d4748db0559","repo":"quarkusio/quarkus","slug":"dpop-proof-token-signature-is-invalid","errorCode":null,"errorMessage":"DPoP proof token signature is invalid","messagePattern":"DPoP proof token signature is invalid","errorType":"exception","errorClass":"AuthenticationFailedException","httpStatus":401,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java","lineNumber":298,"sourceCode":"                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));\n                            }\n\n                            byte[] jwkProofDigest = publicJsonWebKey.calculateThumbprint(\"SHA-256\");\n                            String jwkProofThumbprint = OidcCommonUtils.base64UrlEncode(jwkProofDigest);\n\n                            if (!dpopJwkThumbprint.equals(jwkProofThumbprint)) {\n                                LOG.warn(\"DPoP access token JWK thumbprint does not match the DPoP proof JWK thumbprint\");\n                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));\n                            }\n\n                            try {\n                                JsonWebSignature jws = new JsonWebSignature();\n                                jws.setAlgorithmConstraints(OidcProvider.ASYMMETRIC_ALGORITHM_CONSTRAINTS);\n                                jws.setCompactSerialization((String) requestData.get(OidcUtils.DPOP_PROOF));\n                                jws.setKey(publicJsonWebKey.getPublicKey());\n                                if (!jws.verifySignature()) {\n                                    LOG.warn(\"DPoP proof token signature is invalid\");\n                                    throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));\n                                }\n                            } catch (JoseException ex) {\n                                LOG.warn(\"DPoP proof token signature can not be verified\");\n                                throw new AuthenticationFailedException(ex, invalidDPoPProofMap(request.getToken()));\n                            }\n\n                            JsonObject proofClaims = (JsonObject) requestData.get(OidcUtils.DPOP_PROOF_JWT_CLAIMS);\n\n                            // Calculate the access token thumprint and compare with the `ath` claim\n\n                            String accessTokenProof = proofClaims.getString(OidcConstants.DPOP_ACCESS_TOKEN_THUMBPRINT);\n                            if (accessTokenProof == null) {\n                                LOG.warn(\"DPoP proof access token hash is missing\");\n                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));\n                            }\n\n                            String accessTokenHash = null;\n                            try {","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java#L280-L316","documentation":"Thrown when the DPoP proof JWT's signature fails verification (jose4j's JsonWebSignature.verifySignature() returns false) against the public key from the proof's own jwk header. The proof payload or header was modified after signing, or it was signed with a different key than the one declared in the header.","triggerScenarios":"A middleman or buggy client rewrites proof claims (iat, htu, nonce) after signing; the client signs with one key but embeds another in the jwk header; corrupted encoding of the compact serialization.","commonSituations":"Proxies that re-sign or re-serialize the DPoP header; home-grown proof builders that serialize claims after signing; clock-adjustment code that mutates the iat claim post-signature.","solutions":["Fix the proof generator to compute the compact JWS only after all header and claim values are final.","Verify the jwk header key is exactly the key used to sign the proof.","Avoid proxies/interceptors that modify the DPoP header or its inputs after signing.","Catch AuthenticationFailedException, regenerate the proof, and retry once with a freshly signed JWT."],"exampleFix":"// before: mutate claims after signing\njws.setPayload(claims); jws.sign();\nclaims.put(\"iat\", now); // invalidates signature\n\n// after: finalize claims, then sign\nclaims.put(\"iat\", now);\njws.setPayload(claims);\njws.sign();","handlingStrategy":"try-catch","validationCode":"// Verify the proof signature locally before sending\nJsonWebSignature jws = new JsonWebSignature();\njws.setCompactSerialization(proof);\njws.setKey(publicKey);\nif (!jws.verifySignature()) throw new IllegalStateException(\"Proof was not signed with the jwk header key\");","typeGuard":null,"tryCatchPattern":"try {\n    return callWithProof();\n} catch (AuthenticationFailedException e) {\n    return callWithProofWithFreshlySignedJwt(); // rebuild proof, sign last\n}","preventionTips":["Sign only after all header and claim values are final","Ensure the jwk header key is the exact signing key","Keep proxies/interceptors from mutating the DPoP header","Generate a new proof per request with fresh iat/jti"],"tags":["oidc","dpop","signature-verification","jwt"],"backgroundTag":"jwt-signature-verification-failed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}