Comments (28)
Sounds reasonable - I support this proposal.
from draft-dpop.
I'm not sure I follow the specifics of the proposal 100% but I think I might support it :)
More generally however, I think we need to take a step back and try to rationalize the whole model a bit. Just by way of example, right now for protected resource access in -01, the full JWK is being sent twice - in both the access token and the proof token. Unless I'm misunderstanding something, which is always possible. And speaking of misunderstanding, maybe I'm just not smart enough to get it but the distinction between the proof and the binding tokens isn't clear to me.
For protected resource access we need:
- the JWK (public key)
- the proof (to some degree of proof anyway)
- the access token bound to the JWK
The three can be combined or separated in various different ways.
For token requests, I think we need to decide how much of what was done for protected resource access can or should be reused. Or if we want to have more OAuth application specific stuff there.
If we lean towards the former (i.e. making token requests similar to protected resource requests), and I feel like the draft is tending that way now in -00/01, I think we can move towards something that has a lot of parallels to token binding where the key and proof of possession are sent with each request in the sec-token-binding
header and then binding of token(s) to the key and/or checking the binding in a token is done as needed. In DPoP, the JWK + the proof (could be together or separate) headers could serve a function similar to the sec-token-binding
header. Then tokens are bound to the jwk by the AS and the binding in tokens is verified by protected resources with the proof part being the same for both. The JWK could be in the proof as a jwk header. Or a separate HTTP header in both cases with the access token containing a JWK thumbprint as the confirmation (I think this is most similar to OAuth Token Binding and MTLS). Or the access token could contain the whole JWK as the confirmation and the HTTP header JWK could be omitted in protected resource requests. There are probably tradeoffs for every approach.
Of course, there are other ways to skin the cat but I've rambled incoherently for too long already in what is probably the wrong place. So I'm going to stop for now. But I'm wondering if maybe trying to find some time for a synchronous discussion might be a good idea...
from draft-dpop.
the full JWK is being sent twice - in both the access token and the proof token. Unless I'm misunderstanding something, which is always possible.
My understanding was that only the binding JWT included the JWK. After that, the bound JWK was represented by the access token and you send a proof JWT. Representing the JWK in the proof JWT just means more traffic and that some implementations will verify the proof wrong.
My proposal is to always externalize the key, so it winds up being the same proof JWT format in the code grant, refresh grant, and resource access cases.
from draft-dpop.
So if I am understanding the proposal correctly, we would have something like this:
Token Request with Code
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Key: <JWK-1> ...
DPoP-Binding: <Proof-1> ...
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
with
JWK-1:
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}
and Proof-1:
{
"typ": "dpop+jwt",
"alg": "ES512",
}.{
"jti": "HK2PmfnHKwXP",
"http_method": "POST",
"http_uri": "https://server.example.com/token",
"exp": 1555555555,
"cnf":{
"dpop+kid": "<JWK Thumbprint of JWK-1>"
}
}
signed using JWK-1.
Resource Access
POST /resource HTTP/1.1
Host: resource-server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-1b> ...
Authorization: Bearer <AT>
where AT would contain the same cnf
claim as in Proof-1 (referring to JWK-1) and Proof-1b would look like Proof-1 except for the http_uri
claim.
Token Request with Refresh Token
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-1> ...
DPoP-Key: <JWK-2> ...
DPoP-Binding: <Proof-2> ...
grant_type=authorization_code
&refresh_token=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
Here, Proof-2 would refer to and be signed using JWK-2. The AS would bind the new AT and RT to the key in JWK-2. Proof-1 is needed to prove the binding for the refresh token.
Delivering the keys would be optional if the receiver knows them already (e.g., because some other mechanism was used to transport them).
Does that look like what you intended, @dwaite ?
from draft-dpop.
I think using kid as the thumbprint can lead to wrong implementations since a kid can be chosen, not just calculated. Using it would mean a kid must either be forbidden or ignored when present in the DPoP-Key header.
from draft-dpop.
I think using kid as the thumbprint can lead to wrong implementations since a kid can be chosen, not just calculated. Using it would mean a kid must either be forbidden or ignored when present in the DPoP-Key header.
I took that idea from RFC7638 (JWK Thumbprint), which says that
The
resulting hash value can be used for identifying or selecting the key
represented by the JWK that is the subject of the thumbprint, for
instance, by using the base64url-encoded JWK Thumbprint value as a
"kid" (key ID) value.
Yes, kid
s can be fabricated, but then again, there is no specified namespace for kid
s; the implementation must have some knowledge about its contents anyway.
from draft-dpop.
kid defined in 7517 is an arbitrary string. We can use the thumbprint calculation for binding for sure but reusing kid just welcomes someone using a client supplied value and the RS blindly trusting it, not recalculating it.
from draft-dpop.
Is there any other canonical claim name for this purpose?
from draft-dpop.
IIRC no, we’d have to make up one.
from draft-dpop.
My understanding was that only the binding JWT included the JWK. After that, the bound JWK was represented by the access token and you send a proof JWT.
You are right, my mistake. I'd overlooked the "(REQUIRED for DPoP Binding JWTs, OPTIONAL for DPoP Proof JWTs)" qualification in the Confirmation claim bullet in section 4. (noting again that the cnf claim isn't the right place for the verification key but I digress).
from draft-dpop.
Representing the JWK in the proof JWT just means more traffic
Well, the JWK needs to be presented to the RS in some way. It could be in the proof (or next to it in a different header) and the access token bound to it with hash (cnf with a jwk thumbprint). Or the JWK could be fully in the access token (binding the AT to the key via a cnf with jwk) and the proof implicitly relies on it. So the JWK with/in the proof is a little more traffic but not much more.
and that some implementations will verify the proof wrong.
I'm sure some implementations will do things wrong regardless. But curious why you seem to suggest this is more error prone?
from draft-dpop.
Is there any other canonical claim name for this purpose?
https://tools.ietf.org/html/rfc7800#section-3.4 defines the cnf
method kid
and the end of that section has:
The content of the "kid" value is application specific. For instance, some applications may choose to use a JWK Thumbprint [JWK.Thumbprint] value as the "kid" value.
Our application of it in the DPoP context could specify that the value of the kid
method cnf
be the SHA256 JWK Thumbprint. That would probably be fine in context but if there's concern about using 'kid' or want to be more explicit then this doc could define and register a new cnf
method like jkt#S256
or similar that is explicitly the SHA256 JWK Thumbprint. Note this is very similar to MTLS with x5t#S256
https://tools.ietf.org/html/draft-ietf-oauth-mtls-13#section-3.2 and token binding with tbh
https://openid.net/specs/openid-connect-token-bound-authentication-1_0-04.html#Representation / https://tools.ietf.org/html/draft-ietf-oauth-token-binding-08#section-3.4
Having said all that, the cnf
claim should only be used to bind the access token to a key. It's not really appropriate in the proof. (#23 is related to this and seems to be where the idea to use cnf to indicate the key that was used to sign to given token/proof).
from draft-dpop.
My proposal is to always externalize the key, so it winds up being the same proof JWT format in the code grant, refresh grant, and resource access cases.
+1 here. And also other grants.
from draft-dpop.
As I understanding the proposal and/or inject my own thoughts onto it, we would have something like this:
Token Request with Code
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Key: <JWK> ...
DPoP-Proof: <Proof-a> ...
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIAWTF
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere
with
JWK:
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}
and Proof:
{
"typ": "dpop+jwt",
"alg": "ES256",
}.{
"jti": "HK2PmfnHKwXP",
"http_method": "POST",
"http_uri": "https://server.example.com/token",
"exp": 1555555555,
}
signed using JWK (that link is implied but maybe could also be indicated by a kid header with something "DPoP-Key" or a thumbprint).
Resource Access
POST /resource HTTP/1.1
Host: resource-server.example.com
Content-Type: application/json
DPoP-Proof: <Proof-b> ...
Authorization: Bearer <AT>
{ ... }
where the AT would contain (or reference via introspection) a cnf
claim with a jwk
member with the JWK from the previous step. Something like:
{"typ":"at+JWT","alg":"RS256","kid":"some AS key"}.
{
"sub": "danielfett",
"exp": 1555555655,
"client_id": "someclientidhere",
... etc ...,
"cnf" : {
"jwk" : {
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}
}
}
Proof-b is just a regular proof for the given request:
{
"typ": "dpop+jwt",
"alg": "ES256",
}.{
"jti": "_0accESWLTCbwc",
"http_method": "POST",
"http_uri": "https://resource-server.example.com/resource",
"exp": 1555555559,
}
Token Request with Refresh Token
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-c> ...
DPoP-Key: <JWK> ...
grant_type=authorization_code
&refresh_token=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere
Proof-c is again a regular proof for the given request:
{
"typ": "dpop+jwt",
"alg": "ES256",
}.{
"jti": "cc1GmfnYwi16",
"http_method": "POST",
"http_uri": "https://server.example.com/token",
"exp": 1555555660,
}
And JWK is the same key the client has been using:
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}
One could assume the full key is stored with and bound to the refresh token so DPoP-Key could be omitted here. However I think more uniformity for calls to the AS is probably preferable and the AS could store just a hash of the key with the RT.
Delivering the key could be optional if the receiver knows them already (e.g., because some other mechanism was used to transport them). In effect that's what is happening with protected resource access.
For now, I've put aside binding ATs to a new key on refresh. That's a separate issue that should be discussed independently.
from draft-dpop.
I'm sure some implementations will do things wrong regardless. But curious why you seem to suggest this is more error prone?
If you include a copy of the key in the JWT, it makes it more likely that an implementor will take the key from the JWT and evaluate the signature in isolation, ignore evaluating the trust relationship pf the key (if persistent such as part of the client registration) or binding (if ephemeral).
from draft-dpop.
more likely that an implementor will take the key from the JWT and evaluate the signature in isolation
fair speculation
from draft-dpop.
I like the idea of identifying the key in the proof/binding JWTs using a new jkt#S256
header. The JWK can then be sent in a header or via other mechanisms.
In the HTTP headers, we should distinguish between DPoP-Proof and DPoP-Binding to enable rotation.
Therefore, we would have something like this:
Token Request with Code
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Key: <JWK-1> ...
DPoP-Binding: <Proof-1> ...
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIAWTF
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere
with
JWK-1:
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}
and Proof:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jkt#S256": <thumbprint of JWK-1>
}.{
"jti": "HK2PmfnHKwXP",
"http_method": "POST",
"http_uri": "https://server.example.com/token",
"exp": 1555555555,
}
signed using JWK-1.
- If JWK-1 is known to the server through an external mechanism, the
DPoP-Key
header can be omitted. - Tokens issued at the token endpoint are always bound to the key for which the client has proven ownership in the
DPoP-Binding
header.
Resource Access
POST /resource HTTP/1.1
Host: resource-server.example.com
Content-Type: application/json
DPoP-Proof: <Proof-2> ...
Authorization: Bearer <AT>
{ ... }
where the AT would contain (or reference via introspection) a cnf
claim with a jwk
member with the JWK from the previous step. Something like:
{"typ":"at+JWT","alg":"RS256","kid":"some AS key"}.
{
"sub": "danielfett",
"exp": 1555555655,
"client_id": "someclientidhere",
... etc ...,
"cnf" : {
"jwk" : {
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}
}
}
Proof-2 is just a regular proof for the given request:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jkt#S256": <thumbprint of JWK-1>
}.{
"jti": "_0accESWLTCbwc",
"http_method": "POST",
"http_uri": "https://resource-server.example.com/resource",
"exp": 1555555559,
}
Token Request with Refresh Token
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-3> ...
DPoP-Binding: <Proof-4> ...
DPoP-Key: <JWK-2> ...
grant_type=authorization_code
&refresh_token=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere
Proof-3 is again a regular proof for the given request:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jkt#S256": <thumbprint of JWK-1>
}.{
"jti": "cc1GmfnYwi16",
"http_method": "POST",
"http_uri": "https://server.example.com/token",
"exp": 1555555660,
}
JWK-2 is a new JWK (not known to the server yet).
Proof-4 is a proof for that JWK:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jkt#S256": <thumbprint of JWK-2>
}.{
"jti": "2bwsGmfnYwi6",
"http_method": "POST",
"http_uri": "https://server.example.com/token",
"exp": 1555555660,
}
- DPoP-Binding and DPoP-Key can be omitted when refreshing tokens; the previous binding is then re-used, i.e., tokens are bound to JWK-1.
✓ Same format for binding and proof
✓ Support for key rotation
✓ Key is always externalized
✓ Compatible with externally exchanged keys
✓ Not using cnf
in the proof/binding JWTs any longer
from draft-dpop.
Again, I think we should eliminate the distinction between proof and binding. They are the same thing. Eventually, if this work gets accepted and progresses in the IETF, it will have to register the http headers and I suspect it will be difficult to argue that two different headers are needed that are basically the same thing.
It could even just be called DPoP
.
I question the need for supporting for key rotation on refresh. Or at least would prefer that we settle on the basic working model before trying to tackle that.
from draft-dpop.
I think we should eliminate the distinction between proof and binding.
But can we combine the two while still allowing for DPoP key rotation?
from draft-dpop.
can we combine the two while still allowing for DPoP key rotation?
Sure, the could be multiple occurrences of the DPoP/JWK headers to facilitate the case of DPoP key rotation on refresh.
from draft-dpop.
In the context of pointing to the key from the proof token, using kid
and describing how it's used is probably sufficient. If it's even needed.
from draft-dpop.
In the context of pointing to the key from the proof token, using
kid
and describing how it's used is probably sufficient. If it's even needed.
"describing how it's used" as in "proof" vs. "binding"?
from draft-dpop.
"describing how it's used" as in "proof" vs. "binding"?
No, sorry if I'm not being very clear. In the previous examples there was something like "jkt#S256": <thumbprint of JWK>
in the proof JWT header. Which is a pointer to the key that was used to sign it (or to the public key that can verify). What I was saying is that in that context you probably don't need a new header (which would need to be defined and registered) but could rather just use the existing kid
header and say that in the given application its value is a hash (or truncated hash even) of the JWK thumbprint of the JWK that signed the token.
Such a pointer arguably isn't even needed as the key to use is implied most/all of the time. But something like it might be useful when the key is referenceable in a client's registeredjwks
or jwks_uri
rather than an ephemeral(ish) key sent with the request. Or to differentiate when there are multiple potential keys.
from draft-dpop.
Sure, the could be multiple occurrences of the DPoP/JWK headers to facilitate the case of DPoP key rotation on refresh.
This works but might be confusing for implementers. I don't think order can be guaranteed when duped headers get foo|, |foo
concatenated by most web frameworks i've used in the past.
from draft-dpop.
No, sorry if I'm not being very clear. In the previous examples there was something like
"jkt#S256": <thumbprint of JWK>
in the proof JWT header. Which is a pointer to the key that was used to sign it (or to the public key that can verify). What I was saying is that in that context you probably don't need a new header (which would need to be defined and registered) but could rather just use the existingkid
header and say that in the given application its value is a hash (or truncated hash even) of the JWK thumbprint of the JWK that signed the token.
If we want to use the thumbprint to refer to the key in the proof, I would still go for jkt#S256
to make very clear what the contents of that header field are supposed to be. We further avoid confusion between kid
s that are hashes of the keys and kid
s as provided in a JWKs (that are not necessarily hashes of the respective keys). Making this distinction clear avoids implementation errors.
But, we might just settle for kid
to refer to the key in the proof and reference whatever key IDs are provided by the client.
Thoughts?
(Keep in mind, we are only talking about the proof now, not the cnf
in the token.)
from draft-dpop.
I question the need for supporting for key rotation on refresh.
Let's discuss this in Issue #40
from draft-dpop.
Thoughts?
Agreed that making the distinction clear would help avoid implementation errors.
However, I also think that simplification and consistency (to the extent possible) will do more to avoid implementation errors than most anything else. As such I'd revisit the model with that in mind and suggest something like this:
All requests (token endpoint and protected resource) from the client would have a DPoP
HTTP header with a value that is a JWT (with the claims we've more or less already established pending the outcome of questions like exp
vs. iat
). The JWT header would have a jwk
JWS Header Parameter (https://tools.ietf.org/html/rfc7515#section-4.1.3) that carries the public potion of the key that was used to sign the JWT and about which proof is being presented regarding the possession of the associated private key. Thus the DPoP
HTTP header delivers the public key and proves possession of the corresponding private key (with the caveats of what proof means in this context).
Access tokens would be bound to the dpop key with (or reference via introspection) a cnf
claim with a jkt#S256
member that has the no-padded-base64url-encoded SHA256 hash of the RFC7638 JWK thumbprint. Protected resources would verify that binding.
Refresh tokens issued to public clients would also be bound to the dpop key.
from draft-dpop.
I like the proposal by @b---c and agree that simplification will prevent errors. Version -02 of the draft will contain what Brian suggested.
from draft-dpop.
Related Issues (20)
- Requirement for servers to reject DPoP proofs that contain private keys HOT 2
- Requests are HTTP, not HTTPS HOT 1
- There is no charset parameter defined for x-www-form-urlencoded HOT 1
- Remove redundant normative language HOT 7
- Reference "confidential client" from oauth2 ? HOT 1
- a client MAY send a DPoP-bound access token using the `Bearer` scheme upon receipt of a `WWW-Authenticate HOT 3
- Editorial: nonces HOT 6
- Artwork line wrapping per RFC 8792 HOT 2
- Other methods to bound a PK with an AT HOT 3
- Import token68 syntax, don't replicate it HOT 1
- No normative language in security considerations HOT 2
- Considerations for new authentication schemes HOT 4
- Considerations on stripping query parameter from requests HOT 3
- little more detail on PKCE/dpop_jkt and code injection
- Resource servers and new nonce values via 200 OK HOT 2
- add note(s) about exposing/allowing headers w/ CORS HOT 4
- RFC723x are now superseeded by RFC9110
- AS providing new nonce in authorization code grant should not consume authorization code HOT 3
- ref JWT BCP HOT 1
- Mention correlation attack HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from draft-dpop.