Subject: Re: [OAUTH-WG] [EXTERNAL] Re: dpop_jkt Authorization Request Parameter

From: Will Bartlett <[email-masked]>
Date: Wed, 5 Jan 2022 02:24:05 +0000

Hi Warren,
For (1), I will try to be more concrete using a variation of Microsoft's system on mobile for public clients - but it is a bit of a long explanation. Microsoft OAuth applications are assigned a random ID (for example, we'll use 1-2-3-4). If they use iOS, they add a redirect_uri=microsoft-ios-1-2-3-4://sign-in - that is a URI whose scheme consists of a prefix and the OAuth application ID. They'll also update their app registration to indicate that their app handles that scheme. For sign-in, instead of opening a normal web browser with the OAuth parameters, the application opens a specific Microsoft-owned native application (which we call "broker"), with the standard OAuth parameters. The broker application uses Apple native APIs to verify the native application identity, and then makes the requested OAuth request, augmented with the user's SSO state, and then returns the OAuth result to the original application on the registered scheme using IPC. For the OAuth request, this broker application supports the standard transport (query / form post url encoded, cookies for user SSO), but it also supports an enhanced (MITM-resistant) transport, modeled after OpenID Connect's "request" parameter, where the OAuth parameters are instead sent as a JWS, using a user key (which is generated at first sign-in), and the user's SSO state is similar put inside the JWS (instead of, e.g. a browser cookie). From a user perspective, the way this works out is that the user signs into that special broker application once, the first time they use the device, and it provides SSO to other applications. If the user is MITMed, the MITM can't interfere in the communication between the end-application and the broker application (as that's on-box) which includes the dpop_jwk, and the request from the broker application to the identity server is signed over in a JWS, so it can't change the dpop_jwk. The MITM can tell the broker that the user's credentials are expired (in an attempt to force the user to re-bootstrap - note that the bootstrap moment is inherently MITM-able), but then the user has to be physically present at the device and willing to enter credentials, and it would even be possible for the broker to indicate to the server that it is a re-bootstrap rather than an initial bootstrap (unless the user uninstalls and re-installs the broker). From my perspective then, in summary, as long as we are talking about off-box threats, the _earlier_ you can bind the user to a cryptographic binding and the more uncommon and intrusive (prompts, notifications, etc.) you can make true cryptographic bootstrap moments, the more types of these threats you can prevent. "dpop_jwk" is general purpose glue to connect _whatever_ binding the SSO state already has with DPoP.

This is all proprietary, but 1) we're working fixing that 2) I do think you can construct similar MITM-resistant profiles from things that are standard.

Re distributed systems - not sure if it's impossible, but it's certainly hard. My comment is more about complexity - distributed systems for single-use auth codes are much more complex (e.g. by lines of code, number of configurations) as cryptography for the same purpose. More complexity means more bugs, harder auditing, etc.

I'm not 100% sure I follow the comment w.r.t confidential clients. The way I had envisioned DPoP working on a confidential client was for e.g. an ASP.NET website. On first (unauthenticated request), the website would generate a CSRF cookie, OpenID Connect nonce, and DPoP key, store all of these in the session store (encrypted cookie or backend session store with session id cookie) together, and then communicate them all to the AS on a 302 redirect (in the "state", "nonce", and "dpop_jkt" parameters, respectively). When the authorization code response comes from the OAuth provider, the website would use state to locate the correct session from the store, and redeem the authorization code using a DPoP header signed with the DPoP key from that session, check the nonce in the resulting id_token, and then persist the RT and id_token to the session store. You mention "more than one entity" - can you get a bit more concrete here? Similarly, I think this should work for native clients as well as SPAs, except that that all this data is kept in memory rather than in a session store. I do take your point that dpop_jkt should be optional for clients - no pushback there. I worry a bit about the language you suggest "the dpop_jwk MUST NOT be required by AS". At Microsoft, app registrations have rather a lot of properties - it's possible that we might expose a property like "bool requiresDpopJwk" and allow the app developer to configure it - we do this in rather a lot of places (e.g. disabling the implicit flow). DPoP being an optional extension in the first place - I'm not sure what meaning such a statement would really have. It seems desirable in general for implementation to support converting bearer RTs to bound RTs on the fly - we'd probably always allow a DPoP proof to be provided on token if the incoming credential (AC or RT) is unbound.

I'm usure about generalizing the parameter. I might have proposed calling the parameter "dpop" and making its value exactly the "DPoP" header sent to /token - i.e. this is really just DPoP - but browsers with 2k URL length limits never quite seem to vanish entirely. So, DPoP coupling - expected, JWK coupling - already present in DPoP, lack of multiple - already present in DPoP, other needs - non-goal, lack of consistency - agree this is a shortcoming, but couldn't solve it. FWIW, I'm not strongly opposed to generalizing, just worried about adding complexity.

I don't follow w.r.t DPoP signature on the AS without assuming that AS and RS will adopt DPoP at the same time, which I would prefer to avoid. DPoP describes a downgrade path so DPoP-unaware RS can work with DPoP-aware AS under "Compatibility with the Bearer Authentication Scheme". If the AS doesn't check the DPoP signature, then a user gains no protection with a DPoP-aware AS and DPoP-unaware RS, instead of gaining partial protection (protection for refresh tokens but not access tokens). I think that partial protection is worthwhile - 90 day refresh tokens, 1 hour / 1 day access tokens, 5 minute DPoP proofs - 1 hour or 1 day is still a substantial gain on 90 days, even if it doesn't get all the way to 5 minutes.

Thanks,
Will