-
Notifications
You must be signed in to change notification settings - Fork 47
Description
Now that custom token schemes are supported, this issue is to discuss adding a DPoP verifier to the smallrye-jwt code. We could also discuss implementing this as a quarkus extension, but I'd like to propose implementing the verifier at this layer.
The approach that I'd like to propose would be implemented as a i.s.j.auth.jaxrs.DPoPAuthenticationFilter class. This filter would have a @Priority(AUTHENTICATION) and perform the following validation steps:
- Extract a JWT from the
DPoPheader, if one exists - Parse that token and verify that the embedded public key can be used to verify its signature
- Verify that the
htm(HTTP Method) claim matches the method from the JAX-RS request context - Verify that the
htu(HTTP URI) claim matches the URI from the JAX-RS request context - Verify (this is the crucial step) that the thumbprint of the public key from the DPoP header matches the thumbprint in the
cnf(confirmation) claim of the access token (the injectedJsonWebToken)
The DPoP draft specification does not define how to handle error conditions, should validation fail, but I would propose returning a 401 Unauthorized, since that's what this library already returns when a token can't be validated.
The Proof-of-Possession Key Semantics specification defines several ways that an access token can represent confirmation claims, including:
- Thumprint:
"cnf": { "jkt": "SHA-256 thumbprint" } - Embedded public key:
"cnf": { "jwk": { ... } } - The location of a public key:
"cnf": { "jku": "URL of JWKS", "kid": "key-identifier" } - Key ID reference:
"cnf": { "kid": "key-identifier" }
I would like to propose supporting only the first two options. One of the nice features of DPoP is that it is self-contained and makes use of ephemeral keys, and the third option conflicts with both of those features. The fourth option is complicated because it would require that resource servers have extra out-of-band knowledge and it has questionable practicality in this context.
One part of this that could use some discussion relates to how to prevent against downgrade attacks. Since it would be important to continue supporting regular Bearer tokens in addition to DPoP, one should consider the case where an Authorization: DPoP <token> header gets replayed as Authorization: Bearer <token> without the added DPoP header. Typically, one would want to reject this request, but this assumes that the cnf claim is not being used for any other purpose, and I'm not sure that's a safe assumption in the general case. I suspect the simplest way to address this is the following:
By default, DPoP proofing is entirely disabled, but it can be enabled via an MP-config value (e.g. smallrye.jwt.enable.dpop). If enabled and if the access token contains a cnf claim, then the DPoP filter becomes relevant. At that point, if the token scheme is Bearer, the request can be denied. But if the access token does not contain a cnf claim, then the token is treated as a simple bearer token and DPoP validation doesn't come into play at all.
If this seems like a reasonable approach, I can implement it.