My interpretation is that the audience and scope claims, as other features like nonce, are in place to prevent tokens from being intercepted and misused, not to facilitate passing tokens around.
The purpose of a nonce is to explicitly prevent the token from being reused.
The purpose of the other claims is to prevent them from being accepted (and used) in calls to other services.
If you implement your server correctly, each instance of each service is a principal which goes through auth flows independently and uses its own tokens.
There is no token sharing.
Is it good? I'm not a fan. To use DPoP safely (without replay attacks), you need to add server-side nonces ("nonce") and client-generated nonces ("jti", great and definitely not confusing terminology there).
You need to make sure client-generated nonces are only used once, which requires setting up... wait for it... A database! And if you'll be using DPoP in a distributed manner, with access tokens then, well, a database shared across all services. And this is not an easy-to-scale read-oriented database like you'd have to use for stateful tokens. No, this is a database that requires an equal number of reads and writes (assuming you're not under a DDoS attack): for each DPoP validation, you'd need to read the nonce and then add it to the database. You'd also need to implement some sort of TTL mechanism to prevent the database from growing forever and implement strong rate limitation across all services to prevent very easy DDoS.
It seems like the main driving motivation behind DPoP is to mitigate the cost of refresh tokens being exfiltrated from public clients using XSS attacks, but I believe it is too cumbersome to be used securely as a general mechanism for safe token delegation that prevents "pass-the-token" attacks.
[1] https://developer.okta.com/docs/guides/dpop/nonoktaresources...
Proof tokens can only be used for a narrow window of time (seconds to minutes), so you just need a cache of recently seen token identifiers (jtis) to do replay detection. And proof tokens are bound to an endpoint with the htm and htu claims. They can't be used across services, so I don't see a need for that replay cache to be shared across all services.
Please note that I am talking about using DPoP to verify _every_ request, not just a token refresh request (where OAuth 2.1 is setting DPoP as an alternative to issuing a new refresh token and revoking the old one). When using DPoP for every request, the amount of client-generated nonces ("jti"s) is quite high, since you need a new one for every request.
And yes, you can rely on "htu" to distinguish between services and have a separate nonce cache for every service, but this would require deploying and maintaining additional infrastructure for every service. Depending on your organization this may or may not be an issue, but this is a big issue for us.
What did we decide on instead? Request Signature and Mutual TLS binding (RFC 8705) where possible. Request Signatures without nonces do not work well for repeatable requests (like the Refresh Token Grant), but this is not our use case.
What’s so terrible about that? Several security engineers I trust favor designs with symmetric crypto, e.g. Fly.io https://fly.io/blog/macaroons-escalated-quickly/ and Facebook https://eprint.iacr.org/2018/413.pdf
[1] https://salt.security/blog/oh-auth-abusing-oauth-to-take-ove...
This is why JWTs are used in OIDC (e.g. “Sign-in with Google”: any website can use it, and it doesn’t make Google’s own security weaker.
I’ll concede that small, but important, details like these are not readily understood by those following some tutorial off some coding-camp content-farm (or worse: using a shared-secret for signing tokens instead of asymmetric cryptography, ugh) - and that’s also where we see the vulnerabilities. OAuth2+OIDC is very hard to grok.