RFC 9449

OAuth 2.0 Demonstrating Proof of Possession (DPoP)

๐Ÿ“… published September 2023, from ietf

Okay bestie, let's get into it ๐ŸŽ€โœจ

Title Block and Vibe Check ๐Ÿ’…

Okay so, this is RFC 9449, titled OAuth 2.0 Demonstrating Proof of Possession (DPoP) ๐Ÿฆ‹. It's a Standards Track document, which means it's an official Internet standard, not just a suggestion ๐Ÿ‘‘. It came out in September 2023, and it's giving very serious-but-iconic energy ๐Ÿ”ฅ.

The authors are a whole squad: D. Fett from Authlete, B. Campbell and D. Waite from Ping Identity, J. Bradley from Yubico, T. Lodderstedt from Tuconic, and M. Jones from Self-Issued Consulting ๐ŸŒˆ. Literally a dream team of identity nerds ๐Ÿซถ.

It doesn't obsolete or update any earlier RFCs, so it's a fresh start, no cap ๐ŸŒธ. The ISSN is 2070-1721 if you ever need to look it up ๐Ÿงš.

Abstract โœจ

Okay so the big idea is this: DPoP is a way to sender-constrain OAuth 2.0 tokens using a proof-of-possession mechanism that lives at the application level ๐Ÿ’–. The whole point is that it lets you detect replay attacks on both access tokens and refresh tokens ๐Ÿ˜ญ. Not me stealing a token and using it, lol ๐Ÿ’€.

Status of This Memo โœจ

This is an Internet Standards Track document ๐ŸŽ€. It's a product of the IETF and represents the consensus of the IETF community, and it went through public review and got approved by the IESG ๐Ÿ‘‘. If you want more on Internet Standards, check out Section 2 of RFC 7841 ๐Ÿงš. Errata and feedback live at https://www.rfc-editor.org/info/rfc9449 ๐ŸŒˆ.

Copyright (c) 2023 IETF Trust and the persons identified as the document authors. All rights reserved ๐Ÿ’œ. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as of the publication date ๐Ÿฆ‹. Read those carefully, bestie, because they spell out your rights and restrictions ๐Ÿซถ. Code Components pulled from this doc must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty as described in that license ๐Ÿ”ฅ.

Table of Contents ๐Ÿ“

1.  Introduction
  1.1.  Conventions and Terminology
2.  Objectives
3.  Concept
4.  DPoP Proof JWTs
  4.1.  The DPoP HTTP Header
  4.2.  DPoP Proof JWT Syntax
  4.3.  Checking DPoP Proofs
5.  DPoP Access Token Request
  5.1.  Authorization Server Metadata
  5.2.  Client Registration Metadata
6.  Public Key Confirmation
  6.1.  JWK Thumbprint Confirmation Method
  6.2.  JWK Thumbprint Confirmation Method in Token Introspection
7.  Protected Resource Access
  7.1.  The DPoP Authentication Scheme
  7.2.  Compatibility with the Bearer Authentication Scheme
  7.3.  Client Considerations
8.  Authorization Server-Provided Nonce
  8.1.  Nonce Syntax
  8.2.  Providing a New Nonce Value
9.  Resource Server-Provided Nonce
10. Authorization Code Binding to a DPoP Key
  10.1.  DPoP with Pushed Authorization Requests
11. Security Considerations
  11.1.  DPoP Proof Replay
  11.2.  DPoP Proof Pre-generation
  11.3.  DPoP Nonce Downgrade
  11.4.  Untrusted Code in the Client Context
  11.5.  Signed JWT Swapping
  11.6.  Signature Algorithms
  11.7.  Request Integrity
  11.8.  Access Token and Public Key Binding
  11.9.  Authorization Code and Public Key Binding
  11.10. Hash Algorithm Agility
  11.11. Binding to Client Identity
12. IANA Considerations
  12.1.  OAuth Access Token Types Registration
  12.2.  OAuth Extensions Error Registration
  12.3.  OAuth Parameters Registration
  12.4.  HTTP Authentication Schemes Registration
  12.5.  Media Type Registration
  12.6.  JWT Confirmation Methods Registration
  12.7.  JSON Web Token Claims Registration
    12.7.1.  "nonce" Registration Update
  12.8.  Hypertext Transfer Protocol (HTTP) Field Name Registration
  12.9.  OAuth Authorization Server Metadata Registration
  12.10. OAuth Dynamic Client Registration Metadata
13. References
  13.1.  Normative References
  13.2.  Informative References
Acknowledgements
Authors' Addresses

1. Introduction ๐Ÿฆ‹

1.1. Conventions and Terminology ๐Ÿ’…

Okay so, the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be read as described in BCP 14 [RFC2119] [RFC8174], but only when they show up in all capitals ๐ŸŽ€.

This spec uses the Augmented Backus-Naur Form (ABNF) notation of [RFC5234] ๐Ÿงš.

It also borrows a bunch of terms from "The OAuth 2.0 Authorization Framework" [RFC6749], like "access token", "refresh token", "authorization server", "resource server", "authorization endpoint", "authorization request", "authorization response", "token endpoint", "grant type", "access token request", "access token response", "client", "public client", and "confidential client" ๐ŸŒธ.

The terms "request", "response", "header field", and "target URI" come from [RFC9110] โœจ. The terms "JOSE" and "JOSE Header" come from [RFC7515] ๐Ÿซถ.

Some examples in here are non-normative, which just means they're there to help, not to set rules ๐Ÿ’–. They show partial and complete HTTP messages. Some of them use a single trailing backslash to show a long value wrapping onto the next line, per [RFC8792]. The backslash and the leading spaces on wrapped lines aren't part of the value, so don't count them ๐Ÿ“.

Heads up: when a requirement word isn't in all capitals, it's just regular English and carries no requirement force ๐Ÿ™ˆ.

๐Ÿฆ‹ Part 1 of 9 done. More parts coming, so stay tuned, bestie! ๐Ÿ’œ

2. Objectives โœจ

okay bestie, the main goal of DPoP is to stop random people from using a leaked or stolen access token ๐Ÿ”’. The trick is that we tie the token to a public key when it gets issued, and then whoever uses it has to prove they hold the matching private key ๐Ÿ”‘. So only the legit sender (the one with the private key) can use it, and the server gets extra confidence that the sender is actually allowed to use it ๐Ÿ’…

Access tokens that are sender-constrained with DPoP are different from your typical bearer token, which literally anyone holding it can use ๐Ÿ™ˆ. Protections exist to keep bearer tokens from leaking on purpose, but leaks still happen through bugs and implementation issues in other layers of the protocol or software stack (see, e.g., Compression Ratio Info-leak Made Easy (CRIME) [CRIME], Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext (BREACH) [BREACH], Heartbleed [Heartbleed], and the Cloudflare parser bug [Cloudbleed]). There have also been loads of token theft attacks on OAuth implementations themselves ([GitHub.Tokens] is just one high-profile example ๐Ÿ˜ญ). DPoP gives you general defense in depth against unexpected token leakage ๐Ÿ›ก๏ธ. It is not a replacement for a secure transport, though, and it MUST always be used together with HTTPS ๐Ÿ”.

The whole point of OAuth is that the client has to show the access token to the protected resources it visits ๐Ÿซถ. The attacker model in [SECURITY-TOPICS] covers cases where a protected resource might be counterfeit, malicious, or compromised, and it replays tokens it received against other protected resources to get unauthorized access ๐Ÿ‘€. Audience-restricted access tokens (for example, using the JWT [RFC7519] aud claim) can prevent that kind of misuse. The catch is that doing this in practice has been super cumbersome for lots of deployments (even with extensions like [RFC8707]) ๐Ÿ˜ฉ. Sender-constraining access tokens is a sturdier and more straightforward way to stop token replay at a different endpoint, and DPoP is an accessible application-layer way to do it ๐Ÿฆ‹.

Browser-based OAuth clients have extra things to worry about because of cross-site scripting (XSS) ๐Ÿ•ธ๏ธ. The most straightforward XSS attack is for an attacker to steal a token and use it themselves, totally separate from the legit client. A stolen access token gets used for protected resource access, and a stolen refresh token gets used to get new access tokens. If the private key is non-extractable (which is possible with [W3C.WebCryptoAPI]), DPoP makes exfiltrated tokens useless all by themselves ๐Ÿ™Œ.

XSS can also let an attacker run code inside the browser-based client app and misuse a token indirectly, through the client itself ๐Ÿ˜ˆ. That execution context can use the signing key, so it can produce DPoP proofs to go along with the token. At this application layer there's most likely no feasible defense against this except generally preventing XSS, so it is considered out of scope for DPoP ๐Ÿšซ.

Malicious XSS code running in the browser-based client app can also create DPoP proofs with timestamp values in the future and exfiltrate them along with a token ๐Ÿ’€. Those stolen artifacts can later be used outside the client app to access protected resources. To prevent this, servers can optionally require clients to include a server-chosen value in the proof that an attacker can't predict (a nonce) ๐ŸŽฒ. Without the optional nonce, the damage from pre-computed DPoP proofs is somewhat limited, because the proof is bound to an access token when it's used on a protected resource. A proof covering an access token that doesn't exist yet can't feasibly be created, so access tokens obtained with an exfiltrated refresh token and pre-computed proofs will be unusable ๐Ÿ™…โ€โ™€๏ธ.

Additional security considerations are discussed in Section 11. Don't skip it, it's serious business ๐Ÿง.


3. Concept ๐Ÿง 

The main data structure this spec introduces is a DPoP proof JWT, which gets sent as a header in an HTTP request (more details below) ๐ŸŽ€. A client uses a DPoP proof JWT to prove it holds the private key that corresponds to a certain public key ๐Ÿ”.

Roughly speaking, a DPoP proof is a signature over:

  • some data of the HTTP request it's attached to,
  • a timestamp,
  • a unique identifier,
  • an optional server-provided nonce, and
  • a hash of the associated access token, when an access token is present in the request.

Here's the big picture ๐Ÿ‘‡

+--------+                                          +---------------+
|        |--(A)-- Token Request ------------------->|               |
| Client |        (DPoP Proof)                      | Authorization |
|        |                                          |     Server    |
|        |<-(B)-- DPoP-Bound Access Token ----------|               |
|        |        (token_type=DPoP)                 +---------------+
|        |
|        |
|        |                                          +---------------+
|        |--(C)-- DPoP-Bound Access Token --------->|               |
|        |        (DPoP Proof)                      |    Resource   |
|        |                                          |     Server    |
|        |<-(D)-- Protected Resource ---------------|               |
|        |                                          +---------------+
+--------+

Figure 1: Basic DPoP Flow

The basic steps of an OAuth flow with DPoP (without the optional nonce) are shown in Figure 1 ๐Ÿ“‹.

  • A. In the token request, the client sends an authorization grant (like an authorization code, a refresh token, etc.) to the authorization server so it can get an access token (and maybe a refresh token too). The client attaches a DPoP proof to the request in an HTTP header ๐Ÿ“จ.
  • B. The authorization server binds (sender-constrains) the access token to the public key the client claimed in the DPoP proof. That means the access token can't be used without proving possession of the matching private key ๐Ÿ”—. If a refresh token gets issued to a public client, it's also bound to the public key of the DPoP proof ๐Ÿ”.
  • C. To use the access token, the client has to prove it has the private key by, again, adding a header to the request that carries a DPoP proof for that request ๐Ÿ™‹โ€โ™€๏ธ. The resource server needs to get info about the public key the access token is bound to. That info can be encoded right into the access token (for JWT-structured access tokens) or provided through a token introspection endpoint (not shown here). The resource server checks that the public key the access token is bound to matches the public key of the DPoP proof. It also checks that the access token hash in the DPoP proof matches the access token in the request ๐Ÿ”.
  • D. The resource server refuses to serve the request if the signature check fails or if the data in the DPoP proof is wrong, e.g., the target URI doesn't match the URI claim in the DPoP proof JWT ๐Ÿšซ. The access token itself, of course, also has to be valid in every other way ๐Ÿ’….

The DPoP mechanism in this doc is not a client authentication method ๐Ÿ™ƒ. In fact, a primary use case of DPoP is public clients (like single-page applications and apps on a user's device) that don't use client authentication. That said, DPoP is designed to be compatible with private_key_jwt and all the other client authentication methods ๐Ÿงฉ.

DPoP doesn't directly ensure message integrity. It relies on the TLS layer for that ๐Ÿ”’. See Section 11 for details.


4. DPoP Proof JWTs ๐Ÿฆ‹

DPoP introduces the concept of a DPoP proof, which is a JWT that the client creates and sends with an HTTP request using the DPoP header field ๐Ÿ“ฌ. Each HTTP request requires a unique DPoP proof โ˜๏ธ.

A valid DPoP proof shows the server that the client holds the private key that was used to sign the DPoP proof JWT ๐Ÿ”‘. This lets authorization servers bind issued tokens to the matching public key (as described in Section 5), and lets resource servers verify the key-binding of the tokens they receive (see Section 7.1). That stops those tokens from being used by anyone who doesn't have access to the private key ๐Ÿ™…โ€โ™€๏ธ.

The DPoP proof shows that the sender possesses a key. By itself it is not an authentication or access control mechanism ๐Ÿšง. When it's presented together with a key-bound access token as described in Section 7.1, the DPoP proof adds extra assurance that the client is legit to present the access token. Still, a valid DPoP proof JWT is not enough on its own to make access control decisions ๐Ÿ™ˆ.

4.1. The DPoP HTTP Header ๐ŸŽ€

A DPoP proof gets included in an HTTP request using the following request header field ๐Ÿ’Œ:

  • DPoP: A JWT that adheres to the structure and syntax of Section 4.2.

Figure 2 shows an example DPoP HTTP header field. The example uses "" line wrapping per [RFC8792] โœ‚๏ธ.

DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\
 VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\
 nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\
 QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj\
 oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia\
 WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg\
 4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg

Figure 2: Example DPoP Header

Per [RFC9110], header field names are case insensitive, so DPoP, DPOP, dpop, etc. are all valid and equivalent header field names ๐Ÿค“. Case still matters in the header field value though, so be careful there ๐Ÿ™.

The DPoP HTTP header field value uses the token68 syntax defined in Section 11.2 of [RFC9110] and is repeated below in Figure 3 for easy reference ๐Ÿ“Œ.

DPoP       = token68
token68    = 1*( ALPHA / DIGIT /
                 "-" / "." / "_" / "~" / "+" / "/" ) *"="

Figure 3: DPoP Header Field ABNF

4.2. DPoP Proof JWT Syntax โœจ

okay bestie, so a DPoP proof is a JWT [RFC7519] that gets signed with JSON Web Signature (JWS) [RFC7515] using a private key the client picked itself ๐Ÿ”‘. The JOSE Header of a DPoP JWT MUST contain at least these parameters:

  • typ: a field with the value dpop+jwt, which explicitly types the DPoP proof JWT, as recommended in Section 3.11 of [RFC8725] ๐ŸŽ€
  • alg: an identifier for a JWS asymmetric digital signature algorithm from [IANA.JOSE.ALGS]. It MUST NOT be none or an identifier for a symmetric algorithm (Message Authentication Code (MAC)) ๐Ÿšซ
  • jwk: the public key the client chose, in JSON Web Key (JWK) [RFC7517] format as defined in Section 4.1.3 of [RFC7515]. It MUST NOT contain a private key ๐Ÿ™…

The payload of a DPoP proof MUST contain at least these claims:

  • jti: a unique ID for the DPoP proof JWT ๐Ÿ†”. The value MUST be assigned so there is a negligible probability that the same value gets assigned to any other DPoP proof used in the same context during the time window of validity. You can get that uniqueness by encoding (base64url or any other suitable encoding) at least 96 bits of pseudorandom data, or by using a version 4 Universally Unique Identifier (UUID) string according to [RFC4122]. The server can use the jti for replay detection and prevention, see Section 11.1 ๐Ÿ”
  • htm: the value of the HTTP method (Section 9.1 of [RFC9110]) of the request the JWT is attached to ๐ŸŒธ
  • htu: the HTTP target URI (Section 7.1 of [RFC9110]) of the request the JWT is attached to, without query and fragment parts ๐Ÿฆ‹
  • iat: the creation timestamp of the JWT (Section 4.1.6 of [RFC7519]) โฐ

When the DPoP proof is used alongside an access token in protected resource access (see Section 7), the DPoP proof MUST also contain this claim:

  • ath: the hash of the access token ๐Ÿ’œ. The value MUST be the result of base64url encoding (as defined in Section 2 of [RFC7515]) the SHA-256 [SHS] hash of the ASCII encoding of the associated access token's value.

When the authentication server or resource server gives you a DPoP-Nonce HTTP header in a response (see Sections 8 and 9), the DPoP proof MUST also contain this claim:

  • nonce: a recent nonce provided via the DPoP-Nonce HTTP header ๐ŸŒˆ

A DPoP proof MAY contain other JOSE Header Parameters or claims, as defined by extension, profile, or deployment-specific requirements. so flexible, I'm obsessed โœจ

Figure 4 is a conceptual example showing the decoded content of the DPoP proof in Figure 2. The JSON of the JWT header and payload is shown, but the signature part is left out. As usual, line breaks and extra spaces are added for formatting and readability ๐Ÿ“

{
  "typ":"dpop+jwt",
  "alg":"ES256",
  "jwk": {
    "kty":"EC",
    "x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
    "y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
    "crv":"P-256"
  }
}
.
{
  "jti":"-BwC3ESc6acc2lTc",
  "htm":"POST",
  "htu":"https://server.example.com/token",
  "iat":1562262616
}

Figure 4: Example JWT Content of a DPoP Proof ๐Ÿงš

Of the HTTP request, only the HTTP method and URI go into the DPoP JWT, so only those two message parts are covered by the DPoP proof. The vibe is to sign just enough of the HTTP data to give reasonable proof of possession for the HTTP request. Using only a minimal subset of the HTTP header data avoids the big headaches that come with trying to normalize HTTP messages ๐Ÿ™ˆ. Still, DPoP proofs can be extended to carry other information from the HTTP request (see also Section 11.7) ๐Ÿ’…

4.3. Checking DPoP Proofs โœจ

okay so when a server is validating a DPoP proof, it MUST make sure all of this is true:

  1. There is not more than one DPoP HTTP request header field.
  2. The DPoP HTTP request header field value is a single, well-formed JWT.
  3. All required claims per Section 4.2 are contained in the JWT.
  4. The typ JOSE Header Parameter has the value dpop+jwt.
  5. The alg JOSE Header Parameter indicates a registered asymmetric digital signature algorithm [IANA.JOSE.ALGS], is not none, is supported by the application, and is acceptable per local policy.
  6. The JWT signature verifies with the public key contained in the jwk JOSE Header Parameter.
  7. The jwk JOSE Header Parameter does not contain a private key.
  8. The htm claim matches the HTTP method of the current request.
  9. The htu claim matches the HTTP URI value for the HTTP request in which the JWT was received, ignoring any query and fragment parts.
  10. If the server provided a nonce value to the client, the nonce claim matches the server-provided nonce value.
  11. The creation time of the JWT, as determined by either the iat claim or a server managed timestamp via the nonce claim, is within an acceptable window (see Section 11.1).
  12. If presented to a protected resource in conjunction with an access token:
    • ensure that the value of the ath claim equals the hash of that access token, and
    • confirm that the public key to which the access token is bound matches the public key from the DPoP proof.

To cut down on false negatives, servers SHOULD employ syntax-based normalization (Section 6.2.2 of [RFC3986]) and scheme-based normalization (Section 6.2.3 of [RFC3986]) before comparing the htu claim ๐Ÿงผ

These checks can happen in any order ๐ŸŽ€

Stay tuned, there's more coming! ๐Ÿ’–

5. DPoP Access Token Request โœจ

Okay bestie, so when a client wants an access token bound to a public key with DPoP, it MUST send a valid DPoP proof JWT in a DPoP header with its request to the authorization server's token endpoint. This applies to every access token request, no matter the grant type ๐Ÿ’… (think the super common authorization_code and refresh_token grants, plus extension grants like the JWT authorization grant [RFC7523]). Figure 5 shows an authorization code grant with the DPoP proof JWT chilling in the DPoP header. It uses "" line wrapping per [RFC8792] ๐ŸŽ€

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\
 VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\
 nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\
 QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj\
 oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia\
 WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg\
 4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg

grant_type=authorization_code\
&client_id=s6BhdRkqt\
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb\
&code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-

Figure 5: Token Request for a DPoP Sender-Constrained Token Using an Authorization Code

The DPoP HTTP header field MUST contain a valid DPoP proof JWT. If the proof is invalid, the authorization server sends back an error response per Section 5.2 of [RFC6749], with invalid_dpop_proof as the value of the error parameter. Oof, not a vibe ๐Ÿ’€

Once the server has checked the proof, it sender-constrains the access token by tying the issued token to the public key from that proof. You can do this in any of the ways described in Section 6. A token_type of DPoP MUST be included in the access token response, which tells the client the token is bound to its DPoP key and can be used as described in Section 7.1. Figure 6 is an example of what that response looks like โœจ

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
 "access_token": "Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU",
 "token_type": "DPoP",
 "expires_in": 2677,
 "refresh_token": "Q..Zkm29lexi8VnWg2zPW1x-tgGad0Ibc3s3EwM_Ni4-g"
}

Figure 6: Access Token Response

The response in Figure 6 comes with a refresh token, so the client can get a fresh access token once the old one expires ๐Ÿ”ฅ Refreshing is just a token request using the refresh_token grant type, sent to the authorization server's token endpoint. And just like every access token request, the client makes it a DPoP request by including a DPoP proof, as Figure 7 shows.

Figure 7 uses "" line wrapping per [RFC8792].

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\
 VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\
 nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\
 QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj\
 oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia\
 WF0IjoxNTYyMjY1Mjk2fQ.pAqut2IRDm_De6PR93SYmGBPXpwrAk90e8cP2hjiaG5Qs\
 GSuKDYW7_X620BxqhvYC8ynrrvZLTk41mSRroapUA

grant_type=refresh_token\
&client_id=s6BhdRkqt\
&refresh_token=Q..Zkm29lexi8VnWg2zPW1x-tgGad0Ibc3s3EwM_Ni4-g

Figure 7: Token Request for a DPoP-Bound Token Using a Refresh Token

Now the refresh token drama ๐Ÿ‘€ When an authorization server that supports DPoP hands a refresh token to a public client that sent a valid DPoP proof at the token endpoint, the refresh token MUST be bound to that respective public key. The binding MUST be validated whenever the refresh token gets presented later to fetch new access tokens. That means the client MUST present a DPoP proof for the same key that was used to obtain the refresh token, every single time it uses that refresh token to get a new access token. How the binding is implemented is up to the authorization server. Since the server both creates and validates its own refresh tokens, there's no interoperability consideration for the specific details of the binding, so no stress there ๐Ÿซถ

An authorization server MAY elect to issue access tokens that are not DPoP bound. It signals this to the client with a value of Bearer in the token_type parameter of the access token response, per [RFC6750]. For a public client that also gets a refresh token, this effectively DPoP-binds the refresh token alone, which can level up the security posture even when protected resources haven't been updated to support DPoP ๐ŸŒˆ

If the access token response has a token_type value other than DPoP, the access token protection that DPoP gives you is gone. If that protection matters for your app's security, the client MUST discard the response ๐Ÿ˜ญ Otherwise, the client may carry on like a regular OAuth interaction.

Refresh tokens issued to confidential clients (the ones with established authentication credentials with the authorization server) are not bound to the DPoP proof public key, because they're already sender-constrained by a different mechanism. The OAuth 2.0 Authorization Framework [RFC6749] already requires that an authorization server bind refresh tokens to the client they were issued to, and that confidential clients authenticate when they present a refresh token. So these refresh tokens are sender-constrained through the client identifier and that authentication requirement. This existing approach is more flexible than binding to one specific public key. For example, it lets the client rotate its credentials without invalidating its refresh tokens ๐Ÿ’œ

5.1. Authorization Server Metadata

This document introduces one authorization server metadata parameter [RFC8414]. It signals DPoP support in general, plus the specific JWS alg values the authorization server supports for DPoP proof JWTs.

  • dpop_signing_alg_values_supported: A JSON array containing a list of the JWS alg values (from the [IANA.JOSE.ALGS] registry) supported by the authorization server for DPoP proof JWTs.

5.2. Client Registration Metadata

The Dynamic Client Registration Protocol [RFC7591] defines an API for dynamically registering OAuth 2.0 client metadata with authorization servers. The metadata defined by [RFC7591], along with registered extensions to it, also implies a general data model for clients. That model is useful for authorization server implementations even when the Dynamic Client Registration Protocol isn't in play. These implementations will typically have some kind of user interface for managing client configuration ๐Ÿ“

This document introduces one client registration metadata parameter [RFC7591]. It indicates that the client always uses DPoP when requesting tokens from the authorization server ๐ŸŽ€

  • dpop_bound_access_tokens: A boolean value specifying whether the client always uses DPoP for token requests. If omitted, the default value is false.

If the value is true, the authorization server MUST reject token requests from the client that do not contain the DPoP header. No exceptions, periodt ๐Ÿ’…


6. Public Key Confirmation ๐Ÿฆ‹

Resource servers MUST be able to reliably identify whether an access token is DPoP-bound, and they need enough info to verify the binding to the public key of the DPoP proof (see Section 7.1). This binding gets done by associating the public key with the token in a way the protected resource can access. For example, you can embed the JWK hash directly in the issued access token using the syntax from Section 6.1, or use token introspection as described in Section 6.2. Other ways of associating a public key with an access token are possible too, as long as the authorization server and the protected resource agree on them. Those are beyond the scope of this specification, though ๐Ÿงš

Resource servers supporting DPoP MUST ensure that the public key from the DPoP proof matches the one bound to the access token. Non-negotiable, bestie ๐Ÿ‘‘

6.1. JWK Thumbprint Confirmation Method

When access tokens are represented as JWTs [RFC7519], the public key information is represented using the jkt confirmation method member defined here. To convey the hash of a public key in a JWT, this specification introduces the following JWT Confirmation Method [RFC7800] member for use under the cnf claim โœจ

  • jkt: JWK SHA-256 Thumbprint confirmation method. The value of the jkt member MUST be the base64url encoding (as defined in [RFC7515]) of the JWK SHA-256 Thumbprint (according to [RFC7638]) of the DPoP public key (in JWK format) to which the access token is bound.

The example JWT in Figure 8 has a decoded payload shown in Figure 9, and it contains a cnf claim with the jkt JWK Thumbprint confirmation method member. The jkt value in these examples is the hash of the public key from the DPoP proofs in the examples shown in Section 5. The example uses "" line wrapping per [RFC8792] ๐ŸŒธ

eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9.eyJzdWIiOiJzb21lb25lQGV4YW1\
wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJuYmYiOjE\
1NjIyNjI2MTEsImV4cCI6MTU2MjI2NjIxNiwiY25mIjp7ImprdCI6IjBaY09DT1JaTll\
5LURXcHFxMzBqWnlKR0hUTjBkMkhnbEJWM3VpZ3VBNEkifX0.3Tyo8VTcn6u_PboUmAO\
YUY1kfAavomW_YwYMkmRNizLJoQzWy2fCo79Zi5yObpIzjWb5xW4OGld7ESZrh0fsrA

Figure 8: JWT Containing a JWK SHA-256 Thumbprint Confirmation

{
  "sub":"someone@example.com",
  "iss":"https://server.example.com",
  "nbf":1562262611,
  "exp":1562266216,
  "cnf":
  {
    "jkt":"0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
  }
}

Figure 9: JWT Claims Set with a JWK SHA-256 Thumbprint Confirmation

6.2. JWK Thumbprint Confirmation Method in Token Introspection โœจ

okay so "OAuth 2.0 Token Introspection" [RFC7662] is the method where a protected resource asks an authorization server whether an access token is still active ๐Ÿ” and the resource also figures out metainformation about that token ๐Ÿ’…

for a DPoP-bound access token, the hash of the public key the token is bound to gets handed to the protected resource as metainformation in a token introspection response ๐Ÿฆ‹ it uses the same cnf content with jkt member structure as the JWK Thumbprint confirmation method from Section 6.1, sitting as a top-level member of the introspection response JSON ๐ŸŽ€ note that the resource server doesn't send a DPoP proof with the introspection request, and the authorization server doesn't validate an access token's DPoP binding at the introspection endpoint ๐Ÿ™ˆ instead, the resource server uses the data in the introspection response to validate the access token binding itself locally ๐Ÿ’–

If the token_type member is included in the introspection response, it MUST contain the value DPoP.

the example introspection request in Figure 10 and the matching response in Figure 11 walk through an exchange for the example DPoP-bound access token that was issued in Figure 6 ๐Ÿงš

POST /as/introspect.oauth2 HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic cnM6cnM6TWt1LTZnX2xDektJZHo0ZnNON2tZY3lhK1Rp

token=Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU

                  Figure 10: Example Introspection Request
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "active": true,
  "sub": "someone@example.com",
  "iss": "https://server.example.com",
  "nbf": 1562262611,
  "exp": 1562266216,
  "cnf":
  {
    "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
  }
}

  Figure 11: Example Introspection Response for a DPoP-Bound Access
                               Token

7. Protected Resource Access ๐Ÿ’–

requests to DPoP-protected resources MUST include both a DPoP proof as per Section 4 and the access token as described in Section 7.1 ๐Ÿซถ and the DPoP proof MUST include the ath claim with a valid hash of the associated access token ๐Ÿ”

binding the token value to the proof this way stops a proof from being used with multiple different access token values across different requests ๐Ÿ’… say a client holds tokens bound to two different resource owners, AT1 and AT2, and uses the same key when talking to the authorization server. it's possible those tokens could get swapped ๐Ÿ˜ณ without the ath field to bind it, a captured signature applied to AT1 could be replayed with AT2 instead, changing the rights and access of the intended request ๐Ÿ’€ this same substitution prevention still applies to rotated access tokens within the same combination of client and resource owner, so a rotated token value would need the calculation of a new proof ๐Ÿ”„ this binding also makes sure a proof meant for the access token isn't usable without an access token, or the other way around โœจ

the resource server is required to calculate the hash of the token value presented and verify that it's the same as the hash value in the ath field, as described in Section 4.3 ๐Ÿงฎ since the ath field value is covered by the DPoP proof's signature, including it binds the access token value to the holder of the key used to generate the signature ๐Ÿ—๏ธ

the ath field alone does NOT stop replay of the DPoP proof, and it doesn't bind the proof to the request it's presented in. you still need to check the time window of the proof as well as the included message parameters, such as htm and htu ๐Ÿšจ

Wait, that callout has a capitalised NOT, which is not a BCP 14 keyword. Keep the callout in lowercase to avoid any confusion. The corrected version is below.

the ath field alone does not stop replay of the DPoP proof, and it doesn't bind the proof to the request it's presented in. you still need to check the time window of the proof as well as the included message parameters, such as htm and htu ๐Ÿšจ

7.1. The DPoP Authentication Scheme ๐ŸŽ€

a DPoP-bound access token is sent using the Authorization request header field per Section 11.6.2 of [RFC9110] with an authentication scheme of DPoP ๐Ÿฆ‹ the syntax of the Authorization header field for the DPoP scheme uses the token68 syntax defined in Section 11.2 of [RFC9110] for credentials, and it's repeated below so you don't have to go hunting ๐Ÿ” here's the ABNF notation for DPoP authentication scheme credentials:

token68    = 1*( ALPHA / DIGIT /
                 "-" / "." / "_" / "~" / "+" / "/" ) *"="

credentials = "DPoP" 1*SP token68

             Figure 12: DPoP Authentication Scheme ABNF

for such an access token, a resource server MUST check that a DPoP proof was also received in the DPoP header field of the HTTP request ๐Ÿ“จ it also has to check the DPoP proof according to the rules in Section 4.3, and check that the public key of the DPoP proof matches the public key the access token is bound to per Section 6 ๐Ÿ”‘

The resource server MUST NOT grant access to the resource unless all checks are successful.

figure 13 is an example request to a protected resource, with a DPoP-bound access token in the Authorization header and the DPoP proof in the DPoP header ๐Ÿง it uses "" line wrapping per [RFC8792]. Figure 14 shows the decoded content of that DPoP proof, with the JSON of the JWT header and payload shown and the signature part left out ๐Ÿซง as usual, line breaks and indentation are there for formatting and readability ๐Ÿ’•

GET /protectedresource HTTP/1.1
Host: resource.example.org
Authorization: DPoP Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\
 VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\
 nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\
 QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIj\
 oiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0Z\
 WRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOCwiYXRoIjoiZlVIeU8ycjJaM0RaNTNF\
 c05yV0JiMHhXWG9hTnk1OUlpS0NBcWtzbVFFbyJ9.2oW9RP35yRqzhrtNP86L-Ey71E\
 OptxRimPPToA1plemAgR6pxHF8y6-yqyVnmcw6Fy1dqd-jfxSYoMxhAJpLjA

             Figure 13: DPoP-Protected Resource Request
{
  "typ":"dpop+jwt",
  "alg":"ES256",
  "jwk": {
    "kty":"EC",
    "x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
    "y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
    "crv":"P-256"
  }
}
.
{
  "jti":"e1j3V_bKic8-LAEB",
  "htm":"GET",
  "htu":"https://resource.example.org/protectedresource",
  "iat":1562262618,
  "ath":"fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
}

    Figure 14: Decoded Content of the DPoP Proof JWT in Figure 13

upon receipt of a request to a protected resource within the protection space that requires DPoP authentication, the server can respond with a challenge asking the client for DPoP authentication information ๐Ÿ“ฃ this happens if the request doesn't include valid credentials or doesn't contain an access token that's sufficient for access ๐Ÿ™…โ€โ™€๏ธ the challenge uses the 401 (Unauthorized) response status code ([RFC9110], Section 15.5.2) and the WWW-Authenticate header field ([RFC9110], Section 11.6.1) ๐Ÿ’… the server MAY include the WWW-Authenticate header in response to other conditions as well ๐ŸŒˆ

in such challenges:

  • the scheme name is DPoP.
  • the authentication parameter realm MAY be included to indicate the scope of protection, in the manner described in [RFC9110], Section 11.5.
  • a scope authentication parameter MAY be included, as defined in [RFC6750], Section 3.
  • an error parameter ([RFC6750], Section 3) SHOULD be included to show why the request was declined, if the request included an access token but failed authentication ๐Ÿ™€ the error parameter values described in [RFC6750], Section 3.1 are suitable, and so are any appropriate values defined by extension. the value use_dpop_nonce can be used as described in Section 9 to signal that a nonce is needed in the DPoP proof of a subsequent request(s). also, invalid_dpop_proof is used to show that the DPoP proof itself was judged invalid based on the criteria of Section 4.3.
  • an error_description parameter ([RFC6750], Section 3) MAY be included along with the error parameter, to give developers a human-readable explanation that isn't meant to be shown to end-users ๐Ÿ“
  • an algs parameter SHOULD be included to tell the client which JWS algorithms are acceptable for the DPoP proof JWT ๐Ÿงพ the value is a space-delimited list of JWS alg (Algorithm) header values ([RFC7515], Section 4.1.1).
  • additional authentication parameters MAY be used, and unknown parameters MUST be ignored by recipients ๐Ÿซก

Figure 15 shows a response to a protected resource request without authentication ๐Ÿ‘€

HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP algs="ES256 PS256"

   Figure 15: HTTP 401 Response to a Protected Resource Request without
                           Authentication

Figure 16 shows a response to a protected resource request that was rejected because the DPoP binding in the access token failed confirmation ๐Ÿ’” it uses "" line wrapping per [RFC8792].

HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="invalid_token", \
   error_description="Invalid DPoP key binding", algs="ES256"

 Figure 16: HTTP 401 Response to a Protected Resource Request with
                          an Invalid Token

heads up, browser-based client applications using Cross-Origin Resource Sharing (CORS) [WHATWG.Fetch] only get access to CORS-safelisted response HTTP headers by default ๐Ÿ‘€ so for the app to read and use the WWW-Authenticate HTTP response header value, the server has to make it available by including WWW-Authenticate in the Access-Control-Expose-Headers response header list value ๐Ÿ”“

this authentication scheme is for origin-server authentication only ๐Ÿ  which means this authentication scheme MUST NOT be used with the Proxy-Authenticate or Proxy-Authorization header fields ๐Ÿšซ

one more thing: the syntax of the Authorization header field for this authentication scheme follows the usage of the Bearer scheme defined in Section 2.1 of [RFC6750] โœจ it isn't the preferred credential syntax of [RFC9110], but it's still compatible with the general authentication framework there, and it's used for consistency and familiarity with the Bearer scheme ๐Ÿซถ๐ŸŽ€

7.2. Compatibility with the Bearer Authentication Scheme โœจ

Okay bestie, so if a protected resource is supporting both DPoP and Bearer, it needs to tweak how it checks bearer tokens so nobody can sneak a DPoP-bound access token in as a plain bearer token ๐Ÿ•ต๏ธโ€โ™€๏ธ. Specifically, such a protected resource MUST reject a DPoP-bound access token received as a bearer token per [RFC6750]. No downgrade drama allowed ๐Ÿ’…

Section 11.6.1 of [RFC9110] lets a protected resource show that it supports multiple authentication schemes (that's Bearer and DPoP, babe) using the WWW-Authenticate header field of a 401 (Unauthorized) response ๐ŸŽ€

Now here's the tea ๐Ÿต. A protected resource that only supports [RFC6750] and has no idea DPoP exists would most presumably accept a DPoP-bound access token as a bearer token. Why? JWT [RFC7519] says to ignore unrecognized claims, Introspection [RFC7662] says other parameters might be present without placing any functional requirements on them, and [RFC6750] is basically silent on the content of the access token since it's really about validity ๐Ÿคทโ€โ™€๏ธ. So a client can send a DPoP-bound access token using the Bearer scheme after getting a WWW-Authenticate: Bearer challenge from a protected resource (or it can send a DPoP-bound access token if it already knows what the protected resource can do). The effect of this likely makes phased upgrades to protected resources way easier to pull off ๐Ÿฆ‹, and it helps with long deployments where protected resources handle mixed token types for a while ๐ŸŒˆ

If a protected resource supporting both Bearer and DPoP schemes decides to respond with multiple WWW-Authenticate challenges, it's worth thinking about which challenge(s) should carry the actual error information ๐Ÿ‘€. It is RECOMMENDED that the following rules be adhered to:

  • If no authentication information has been included with the request, then the challenges SHOULD NOT include an error code or other error information, as per Section 3.1 of [RFC6750] (Figure 17) ๐Ÿ™ˆ
  • If the mechanism used to attempt authentication could be established unambiguously, then the corresponding challenge SHOULD be used to deliver error information (Figure 18) ๐Ÿ’œ
  • Otherwise, both Bearer and DPoP challenges MAY be used to deliver error information (Figure 19) โœจ

The following examples use "" line wrapping per [RFC8792] ๐Ÿงš

GET /protectedresource HTTP/1.1
Host: resource.example.org

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer, DPoP algs="ES256 PS256"

Figure 17: HTTP 401 Response to a Protected Resource Request without Authentication

GET /protectedresource HTTP/1.1
Host: resource.example.org
Authorization: Bearer INVALID_TOKEN

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", \
    error_description="Invalid token", DPoP algs="ES256 PS256"

Figure 18: HTTP 401 Response to a Protected Resource Request with Invalid Authentication

GET /protectedresource HTTP/1.1
Host: resource.example.org
Authorization: Bearer Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU
Authorization: DPoP Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU

HTTP/1.1 400 Bad Request
WWW-Authenticate: Bearer error="invalid_request", \
 error_description="Multiple methods used to include access token", \
 DPoP algs="ES256 PS256", error="invalid_request", \
 error_description="Multiple methods used to include access token"

Figure 19: HTTP 400 Response to a Protected Resource Request with Ambiguous Authentication

Heads up, multiple auth methods in one request is a big no-no ๐Ÿšซ. Figure 19 is the "ambiguous" situation, so it gets the 400 treatment ๐Ÿ’€

7.3. Client Considerations ๐ŸŒธ

Authorization including a DPoP proof may not be idempotent (depending on server enforcement of jti, iat, and nonce claims) ๐Ÿค”. Consequently, all previously idempotent requests for protected resources that were previously idempotent may no longer be idempotent. It is RECOMMENDED that clients generate a unique DPoP proof, even when retrying idempotent requests in response to HTTP errors generally understood as transient ๐Ÿ”. Like, every retry gets its own fresh proof, periodt ๐Ÿ’…

Clients that encounter frequent network errors may experience additional challenges when interacting with servers with stricter nonce validation implementations ๐Ÿ˜ญ. Be gentle with yourself on flaky networks, okay? ๐Ÿซถ


8. Authorization Server-Provided Nonce ๐Ÿ”ฎ

This section specifies a mechanism using opaque nonces provided by the server that can be used to limit the lifetime of DPoP proofs ๐Ÿง. Without employing such a mechanism, a malicious party controlling the client (potentially including the end-user) can create DPoP proofs for use arbitrarily far in the future ๐Ÿ’€. Literally a time-travel exploit, not it ๐Ÿ™…โ€โ™€๏ธ

Including a nonce value contributed by the authorization server in the DPoP proof MAY be used by authorization servers to limit the lifetime of DPoP proofs โณ. The server decides when to issue a new DPoP nonce challenge and if it's needed, which means the nonce value has to be used in later DPoP proofs. The logic the server uses to make that call is out of scope of this document ๐Ÿค

An authorization server MAY supply a nonce value to be included by the client in DPoP proofs sent ๐ŸŽ. In this case, the authorization server responds to requests that don't include a nonce with an HTTP 400 (Bad Request) error response per Section 5.2 of [RFC6749], using use_dpop_nonce as the error code value ๐Ÿšจ. The authorization server includes a DPoP-Nonce HTTP header in the response, supplying a nonce value to be used when sending the subsequent request ๐Ÿ’Œ. Nonce values MUST be unpredictable ๐ŸŽฒ. This same error code is used when supplying a new nonce value after a nonce mismatch. The client will typically retry the request with the new nonce value supplied when it gets a use_dpop_nonce error with an accompanying nonce value ๐Ÿ”„

For example, if a token request comes in without a nonce and the authorization server requires one, the authorization server can respond with a DPoP-Nonce value like this to provide a nonce for the DPoP proof:

HTTP/1.1 400 Bad Request
DPoP-Nonce: eyJ7S_zG.eyJH0-Z.HX4w-7v

{
 "error": "use_dpop_nonce",
 "error_description":
   "Authorization server requires nonce in DPoP proof"
}

Figure 20: HTTP 400 Response to a Token Request without a Nonce

Other HTTP headers and JSON fields MAY also be included in the error response, but there MUST NOT be more than one DPoP-Nonce header ๐Ÿงพ. Upon receiving the nonce, the client is expected to retry its token request using a DPoP proof that includes the supplied nonce value in the nonce claim of the DPoP proof ๐ŸŽฏ. Here's an example unencoded JWT payload of such a DPoP proof including a nonce:

{
 "jti": "-BwC3ESc6acc2lTc",
 "htm": "POST",
 "htu": "https://server.example.com/token",
 "iat": 1562262616,
 "nonce": "eyJ7S_zG.eyJH0-Z.HX4w-7v"
}

Figure 21: DPoP Proof Payload including a Nonce Value

The nonce is opaque to the client ๐Ÿซฅ. Don't poke it, don't parse it, just send it back ๐Ÿ™ˆ

If the nonce claim in the DPoP proof doesn't exactly match a nonce recently supplied by the authorization server to the client, the authorization server MUST reject the request ๐Ÿšซ. The rejection response MAY include a DPoP-Nonce HTTP header providing a new nonce value to use for subsequent requests ๐ŸŒˆ

The intent is that clients only need to keep one nonce value and servers need to keep a window of recent nonces ๐Ÿง . That said, transient circumstances may arise in which the stored nonce values for the server and the client differ. Good news though, this situation is self-correcting ๐Ÿช„. With any rejection message, the server can send the client the nonce value it wants to use, and the client can store that nonce value and retry the request with it. Even if the client and/or server throw out their stored nonce values, that's also self-correcting, because new nonce values can be communicated when responding to or retrying failed requests ๐Ÿ”โœจ

Note that browser-based client applications using CORS [WHATWG.Fetch] only have access to CORS-safelisted response HTTP headers by default ๐ŸŒ. For the application to obtain and use the DPoP-Nonce HTTP response header value, the server needs to make it available by including DPoP-Nonce in the Access-Control-Expose-Headers response header list value ๐Ÿ’…

8.1. Nonce Syntax ๐Ÿงฉ

The nonce syntax in ABNF as used by [RFC6749] (which is the same as the scope-token syntax) looks like this:

nonce = 1*NQCHAR

Figure 22: Nonce ABNF

8.2. Providing a New Nonce Value ๐Ÿ’ซ

It's up to the authorization server when to supply a new nonce value for the client to use. The client is expected to use the existing supplied nonce in DPoP proofs until the server supplies a new nonce value ๐Ÿ•ฐ๏ธ

The authorization server MAY supply the new nonce the same way the initial one was supplied: by using a DPoP-Nonce HTTP header in the response. The DPoP-Nonce HTTP header field uses the nonce syntax defined in Section 8.1. Each time this happens, it costs an extra protocol round trip ๐Ÿƒโ€โ™€๏ธ

A more efficient way to supply a new nonce value is also defined, by including a DPoP-Nonce HTTP header in the HTTP 200 (OK) response to the previous request ๐Ÿš€. The client MUST use the new nonce value supplied for the next token request and for all subsequent token requests until the authorization server supplies a new nonce ๐Ÿซก

Responses that include the DPoP-Nonce HTTP header should be uncacheable (for example, using Cache-Control: no-store in response to a GET request) so the response can't be used to serve a later request and a stale nonce value doesn't get used as a result ๐ŸงŠ

Here's an example 200 OK response providing a new nonce value:

HTTP/1.1 200 OK
Cache-Control: no-store
DPoP-Nonce: eyJ7S_zG.eyJbYu3.xQmBj-1

Figure 23: HTTP 200 Response Providing the Next Nonce Value


9. Resource Server-Provided Nonce ๐Ÿ›ก๏ธ

Resource servers can also choose to provide a nonce value to be included in DPoP proofs sent to them ๐Ÿ’–. They provide the nonce using the DPoP-Nonce header in the same way that authorization servers do, as described in Sections 8 and 8.2 ๐ŸŽ€. The error signaling is done as described in Section 7.1. Resource servers use an HTTP 401 (Unauthorized) error code with an accompanying WWW-Authenticate: DPoP value and DPoP-Nonce value to make this happen ๐ŸŒธ

For example, if a resource request comes in without a nonce and the resource server requires one, the resource server can respond with a DPoP-Nonce value like this to provide a nonce for the DPoP proof. The example below uses "" line wrapping per [RFC8792] ๐Ÿงšโ€โ™€๏ธ

HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="use_dpop_nonce", \
  error_description="Resource server requires nonce in DPoP proof"
DPoP-Nonce: eyJ7S_zG.eyJH0-Z.HX4w-7v

Figure 24: HTTP 401 Response to a Resource Request without a Nonce

Note that the nonces provided by an authorization server and a resource server are different, and they shouldn't be mixed up, since nonces will only be accepted by the server that issued them ๐Ÿ™…โ€โ™€๏ธ. Likewise, if a client uses multiple authorization servers and/or resource servers, a nonce issued by any of them should only be used at the issuing server ๐Ÿ—๏ธ. Developers should also be careful not to confuse DPoP nonces with the OpenID Connect [OpenID.Core] ID Token nonce ๐Ÿ‘€. Two nonces, two different vibes, don't mix them up bestie ๐Ÿ’œ

10. Authorization Code Binding to a DPoP Key ๐ŸŽ€

okay so binding the authorization code to the client's proof-of-possession key is giving end-to-end vibes ๐Ÿฆ‹ it lets you lock the whole authorization flow to one key, and this spec defines the dpop_jkt authorization request parameter for exactly that. its value is the JWK Thumbprint [RFC7638] of the proof-of-possession public key using the SHA-256 hash function, which is the same value used for the jkt confirmation method in Section 6.1 โœจ

When a token request comes in, the authorization server works out the JWK Thumbprint of the proof-of-possession public key in the DPoP proof and checks that it matches the dpop_jkt value from the authorization request. If they don't match, it MUST reject the request. no exceptions ๐Ÿ’…

Here's an example authorization request using the dpop_jkt parameter. It uses "" line wrapping per [RFC8792] ๐ŸŒธ

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz\
    &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb\
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM\
    &code_challenge_method=S256\
    &dpop_jkt=NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs HTTP/1.1
Host: server.example.com

Figure 25: Authorization Request Using the dpop_jkt Parameter

Using the dpop_jkt authorization request parameter is OPTIONAL ๐Ÿซถ It MAY also be combined with Proof Key for Code Exchange (PKCE) [RFC7636], which [SECURITY-TOPICS] recommends as a countermeasure against authorization code injection. The dpop_jkt parameter only gives similar protection when a unique DPoP key is used for each authorization request. ๐Ÿ”ฅ

10.1. DPoP with Pushed Authorization Requests ๐Ÿงš

when Pushed Authorization Requests (PARs) [RFC9126] are used alongside DPoP, there are two ways the DPoP key can be communicated in the PAR request ๐Ÿ‘‘

  • The dpop_jkt parameter can be used as described in Section 10 to bind the issued authorization code to a specific key. In this case, dpop_jkt MUST be included alongside the other authorization request parameters in the POST body of the PAR request.
  • Or, the DPoP header can be added to the PAR request. In this case, the authorization server MUST check the provided DPoP proof JWT as defined in Section 4.3. It MUST further behave as if the contained public key's thumbprint was provided using dpop_jkt, i.e., reject the subsequent token request unless a DPoP proof for the same key is provided. This can help simplify the client's implementation, since it can "blindly" attach the DPoP header to all requests to the authorization server regardless of the type of request. It also gives a stronger binding, because the DPoP header contains a proof of possession of the private key. ๐Ÿ’–

Both mechanisms MUST be supported by an authorization server that supports PAR and DPoP. If both mechanisms are used at the same time, the authorization server MUST reject the request if the JWK Thumbprint in dpop_jkt does not match the public key in the DPoP header. ๐Ÿ™ˆ

Allowing both mechanisms means clients using dpop_jkt don't need to tell front-channel and pushed authorization requests apart. At the same time, clients with a single code path for protecting all calls to authorization server endpoints don't need to tell requests to the PAR endpoint apart from requests to the token endpoint โœจ


11. Security Considerations ๐Ÿ’€

bestie, this part is serious, so read it slowly ๐Ÿ™ˆ In DPoP, preventing token replay at a different endpoint (see Section 2) comes from authenticating the server per [RFC6125] and binding the DPoP proof to a particular URI and HTTP method. DPoP protects things a little differently than TLS-based methods such as OAuth Mutual TLS [RFC8705] or OAuth Token Binding [TOKEN-BINDING] (see also Sections 11.1 and 11.7). TLS-based mechanisms can use a tight integration between the TLS layer and the application layer to get strong message integrity, authenticity, and replay protection.

11.1. DPoP Proof Replay ๐Ÿ˜ญ

If an adversary gets hold of a DPoP proof JWT, they could replay it at the same endpoint (the HTTP endpoint and method are enforced through the respective claims in the JWTs). To limit this, servers MUST only accept DPoP proofs for a limited time after they were created (preferably only for a relatively brief period, on the order of seconds or minutes).

In the context of the target URI, servers can store the jti value of each DPoP proof for the time window in which that DPoP proof JWT would be accepted, to prevent multiple uses of the same DPoP proof. HTTP requests to the same URI whose jti value has been seen before would be declined. When strictly enforced, this single-use check gives very strong protection against DPoP proof replay, but it may not always be feasible in practice, e.g., when multiple servers behind a single endpoint have no shared state.

To guard against memory exhaustion attacks, a server that is tracking jti values should reject DPoP proof JWTs with unnecessarily large jti values, or store only a hash of them.

Note: To accommodate clock offsets, the server MAY accept DPoP proofs that carry an iat time in the reasonably near future (on the order of seconds or minutes). Because clock skews between servers and clients may be large, servers MAY limit DPoP proof lifetimes by using server-provided nonce values that contain the time at the server, rather than comparing the client-supplied iat time to the time at the server. Nonces created this way yield the same result even in the face of arbitrarily large clock skews. ๐Ÿ•ฐ๏ธ

Server-provided nonces are an effective way to further reduce the chances of successful DPoP proof replay. Unlike cryptographic nonces, it is acceptable for clients to use the same nonce multiple times, and for the server to accept the same nonce multiple times. As long as the jti value is tracked and duplicates are rejected for the lifetime of the nonce, there is no additional risk of token replay โœจ

11.2. DPoP Proof Pre-generation ๐Ÿ•ต๏ธ

An attacker in control of the client can pre-generate DPoP proofs for specific endpoints arbitrarily far into the future, by choosing the iat value in the DPoP proof to be signed by the proof-of-possession key. And that attacker can be the legitimate user of the client! The user may pre-generate DPoP proofs to exfiltrate from the machine that holds the proof-of-possession key, then copy them to another machine that doesn't have the key. For instance, a bank employee might pre-generate DPoP proofs on a bank computer and copy them to another machine for later use, bypassing bank audit controls. When DPoP proofs can be pre-generated and exfiltrated, all that is actually being proved in DPoP protocol interactions is possession of a DPoP proof, not possession of the proof-of-possession key.

Using server-provided nonce values that attackers can't predict prevents this attack. By providing new nonce values at times of its choosing, the server can limit the lifetime of DPoP proofs and stop pre-generated DPoP proofs from being used. When server-provided nonces are used, possession of the proof-of-possession key is being demonstrated, not just possession of a DPoP proof ๐Ÿ”

The ath claim limits the use of pre-generated DPoP proofs to the lifetime of the access token. Deployments that don't use the nonce mechanism SHOULD NOT issue long-lived DPoP-constrained access tokens. They should prefer short-lived access tokens and refresh tokens instead. Although an attacker could pre-generate DPoP proofs to use the refresh token to get a new access token, they would not realistically be able to pre-generate DPoP proofs for a newly issued access token ๐Ÿ™…โ€โ™€๏ธ

11.3. DPoP Nonce Downgrade ๐Ÿšซ

A server MUST NOT accept any DPoP proofs without the nonce claim when a DPoP nonce has been provided to the client.

11.4. Untrusted Code in the Client Context ๐Ÿ•ท๏ธ

If an adversary can run code in the client's execution context, the security of DPoP is no longer guaranteed. Common issues in web applications that lead to untrusted code execution are XSS and remote code inclusion attacks.

If the private key used for DPoP is stored in a way that it cannot be exported (e.g., in a hardware or software security module), the adversary can't exfiltrate the key and use it to create arbitrary DPoP proofs. The adversary can, however, create new DPoP proofs as long as the client is online, and use these proofs (together with the respective tokens) either on the victim's device or on a device under the attacker's control to send arbitrary requests that servers will accept.

To send requests even when the client is offline, an adversary can try to pre-compute DPoP proofs using timestamps in the future, and exfiltrate these together with the access or refresh token.

An adversary might also try to associate tokens issued by the token endpoint with a key pair under the adversary's control. One way to do this is to modify existing code, e.g., by replacing cryptographic APIs. Another way is to start a new authorization grant between the client and the authorization server in an iframe. This grant needs to be "silent", i.e., not require interaction with the user. With code running in the client's origin, the adversary can access the resulting authorization code and use it to associate their own DPoP keys with the tokens returned from the token endpoint. The adversary can then use the resulting tokens on their own device even if the client is offline. ๐Ÿ˜ฑ

So protecting clients against untrusted code execution is extremely important, even when DPoP is used. Besides secure coding practices, Content Security Policy [W3C.CSP] can be used as a second layer of defense against XSS ๐Ÿ›ก๏ธ

11.5. Signed JWT Swapping ๐Ÿ”

Servers accepting signed DPoP proof JWTs MUST verify that the typ field is dpop+jwt in the headers of the JWTs, so adversaries can't use JWTs created for other purposes.

11.6. Signature Algorithms ๐Ÿ”‘

Implementers MUST ensure that only asymmetric digital signature algorithms (such as ES256) that are deemed secure can be used for signing DPoP proofs. In particular, the algorithm none MUST NOT be allowed.

11.7. Request Integrity ๐Ÿ“ฆ

DPoP does not ensure the integrity of the payload or headers of requests. The DPoP proof only contains claims for the HTTP URI and method, but not the message body or general request headers, for example.

This is an intentional design decision meant to keep DPoP simple to use. But as described, it makes DPoP potentially susceptible to replay attacks where an attacker can modify message contents and headers. In many setups, the message integrity and confidentiality provided by TLS is enough to provide a good level of protection ๐Ÿ”’

Note: While signatures covering other parts of requests are out of the scope of this specification, additional information to be signed can be added into DPoP proofs. โœ๏ธ

11.8. Access Token and Public Key Binding ๐Ÿ”

Okay so the access token gets locked to the DPoP public key (see Section 6) by hashing the JWK representation of that key ๐Ÿ—๏ธ. The hash has to have enough second-preimage resistance so nobody can computationally find or make another key that spits out the same hash value. SHA-256 was picked because it has that property and is widely available. Iconic choice, honestly โœจ.

The DPoP proof gets bound to the access token in a similar way, using a hash of the token as the value of the ath claim in the DPoP proof (see Section 4.2) ๐Ÿ’œ. This only works if the hash is unique enough to reliably identify the access token, and SHA-256's collision resistance covers that. Bestie, the math is holding everything together ๐Ÿซถ.

11.9. Authorization Code and Public Key Binding ๐ŸŽŸ๏ธ

Binding the authorization code to the DPoP public key is specified in Section 10, and it's giving protection ๐Ÿ›ก๏ธ. Without it, an attacker could grab the authorization code, make a DPoP proof with a proof-of-possession key that isn't the client's, and redeem the code with that proof. Binding it end to end means only the client's DPoP key works, so a captured code can't be exfiltrated and used somewhere other than where it was issued. Not on my watch ๐Ÿ’….

Auth codes can get harvested from places where the HTTP messages carrying them are logged ๐Ÿ“œ. Even when servers try to make codes one-time-use, there's often a little time window where attackers can replay them. Like, when authorization servers are scalable replicated services, some replicas might not have the info they need to stop a replay yet ๐Ÿ˜ญ. DPoP binding of the authorization code fixes all of that. Slay.

If an authorization server doesn't (or can't) strictly enforce the single-use limit for codes, and an attacker can get the authorization code (plus the code_verifier if PKCE is used), the attacker can craft a forged token request and bind the resulting token to a key they control ๐Ÿ™ˆ. For example, with XSS, attackers might grab the authorization code and PKCE parameters. Using the dpop_jkt parameter prevents this attack.

Binding the authorization code to the DPoP public key uses a JWK Thumbprint of the key, same as the access token binding does ๐Ÿงฉ. The same JWK Thumbprint considerations apply here too.

11.10. Hash Algorithm Agility ๐ŸŒˆ

The jkt confirmation method member, the ath JWT claim, and the dpop_jkt authorization request parameter all use the output of SHA-256 as their value ๐Ÿ’–. Using a single hash function was intentional, aimed at keeping things simple and avoiding security and interoperability problems that come from common mistakes when people implement parameterized algorithm agility schemes. Not making a fuss over it, just being smart ๐Ÿง .

That said, using a different hash function isn't ruled out if future circumstances change and SHA-256 stops being enough for what this spec needs ๐Ÿ”ฎ. If that ever happens, a short specification is expected that updates this one. Using the output of an appropriate hash function as the value, that spec will likely define a new confirmation method member, a new JWT claim, and a new authorization request parameter. These new items would be used in place of, or alongside, their counterparts in the same message structures and flows of the larger protocol defined here ๐Ÿฆ‹.

11.11. Binding to Client Identity ๐ŸŽ€

When DPoP is used with client authentication, it's only bound to that authentication by being in the same TLS tunnel ๐Ÿš‡. The DPoP proof isn't cryptographically bound to the authentication, so it's possible that the authentication or the DPoP messages got copied into the tunnel ๐Ÿ˜ฌ. Putting the URI in the DPoP can partially reduce that risk, but modifying the authentication mechanism to provide cryptographic binding between authentication and DPoP could offer better protection ๐Ÿ”’. Adding that binding through changes to authentication mechanisms or other means is out of scope for this specification, so it's not handled here ๐Ÿ™ˆ.


12. IANA Considerations ๐Ÿ“‹

12.1. OAuth Access Token Types Registration ๐Ÿชช

IANA has registered the following access token type in the "OAuth Access Token Types" registry [IANA.OAuth.Params], which was established by [RFC6749].

Name:  DPoP

Additional Token Endpoint Response Parameters:  (none)

HTTP Authentication Scheme(s):  DPoP

Change Controller:  IETF

Reference:  RFC 9449

12.2. OAuth Extensions Error Registration ๐Ÿšจ

IANA has registered the following error values in the "OAuth Extensions Error" registry [IANA.OAuth.Params], which was established by [RFC6749].

Invalid DPoP proof:

   Name:  invalid_dpop_proof

   Usage Location:  token error response, resource access error
      response

   Protocol Extension:  Demonstrating Proof of Possession (DPoP)

   Change Controller:  IETF

   Reference:  RFC 9449

Use DPoP nonce:

   Name:  use_dpop_nonce

   Usage Location:  token error response, resource access error
      response

   Protocol Extension:  Demonstrating Proof of Possession (DPoP)

   Change Controller:  IETF

   Reference:  RFC 9449

12.3. OAuth Parameters Registration ๐Ÿ”‘

IANA has registered the following authorization request parameter in the "OAuth Parameters" registry [IANA.OAuth.Params], which was established by [RFC6749].

Name:  dpop_jkt

Parameter Usage Location:  authorization request

Change Controller:  IETF

Reference:  Section 10 of RFC 9449

12.4. HTTP Authentication Schemes Registration ๐Ÿ”

IANA has registered the following scheme in the "HTTP Authentication Schemes" registry [IANA.HTTP.AuthSchemes], which was established by [RFC9110], Section 16.4.1.

Authentication Scheme Name:  DPoP

Reference:  Section 7.1 of RFC 9449

12.5. Media Type Registration ๐ŸŽฌ

IANA has registered the application/dpop+jwt media type [RFC2046] in the "Media Types" registry [IANA.MediaTypes], in the manner described in [RFC6838]. It's used to indicate that the content is a DPoP JWT ๐Ÿ’….

Type name:  application

Subtype name:  dpop+jwt

Required parameters:  n/a

Optional parameters:  n/a

Encoding considerations:  binary.  A DPoP JWT is a JWT; JWT values
   are encoded as a series of base64url-encoded values (some of which
   may be the empty string) separated by period ('.') characters.

Security considerations:  See Section 11 of RFC 9449

Interoperability considerations:  n/a

Published specification:  RFC 9449

Applications that use this media type:  Applications using RFC 9449
   for application-level proof of possession

Fragment identifier considerations:  n/a

Additional information:

   File extension(s):  n/a
   Macintosh file type code(s):  n/a

Person & email address to contact for further information:  Michael
   B. Jones, michael_b_jones@hotmail.com

Intended usage:  COMMON

Restrictions on usage:  none

Author:  Michael B. Jones, michael_b_jones@hotmail.com

Change controller:  IETF

12.6. JWT Confirmation Methods Registration ๐Ÿงพ

IANA has registered the following JWT cnf member value in the "JWT Confirmation Methods" registry [IANA.JWT], which was established by [RFC7800].

Confirmation Method Value:  jkt

Confirmation Method Description:  JWK SHA-256 Thumbprint

Change Controller:  IETF

Reference:  Section 6 of RFC 9449

12.7. JSON Web Token Claims Registration ๐ŸŽซ

IANA has registered the following claims in the "JSON Web Token Claims" registry [IANA.JWT], which was established by [RFC7519].

HTTP method:

   Claim Name:  htm

   Claim Description:  The HTTP method of the request

   Change Controller:  IETF

   Reference:  Section 4.2 of RFC 9449

HTTP URI:

   Claim Name:  htu

   Claim Description:  The HTTP URI of the request (without query and
      fragment parts)

   Change Controller:  IETF

   Reference:  Section 4.2 of RFC 9449

Access token hash:

   Claim Name:  ath

   Claim Description:  The base64url-encoded SHA-256 hash of the
      ASCII encoding of the associated access token's value

   Change Controller:  IETF

   Reference:  Section 4.2 of RFC 9449

12.7.1. "nonce" Registration Update ๐ŸŒธ

The Internet Security Glossary [RFC4949] gives a useful definition of nonce as a random or non-repeating value that's included in data exchanged by a protocol, usually to guarantee liveness and so detect and protect against replay attacks ๐Ÿง.

However, the initial registration of the nonce claim by [OpenID.Core] used language specific to that application, which could limit how widely it applied ๐Ÿค.

So IANA has updated the entry for nonce in the "JSON Web Token Claims" registry [IANA.JWT]. The expanded definition reflects that the claim can be used appropriately in other contexts, and this document is added as a reference, as follows ๐ŸŒŸ.

Claim Name:  nonce

Claim Description:  Value used to associate a Client session with an
   ID Token (MAY also be used for nonce values in other applications
   of JWTs)

Change Controller:  OpenID Foundation Artifact Binding Working Group,
   openid-specs-ab@lists.openid.net

Specification Document(s):  Section 2 of [OpenID.Core] and RFC 9449

12.8. Hypertext Transfer Protocol (HTTP) Field Name Registration ๐Ÿ“จ

IANA has registered the following HTTP header fields, as specified by this document, in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" [IANA.HTTP.Fields], which was established by [RFC9110] ๐Ÿ’Œ.

DPoP:

   Field Name:  DPoP

   Status:  permanent

   Reference:  RFC 9449

DPoP-Nonce:

   Field Name:  DPoP-Nonce

   Status:  permanent

   Reference:  RFC 9449

12.9. OAuth Authorization Server Metadata Registration ๐Ÿ—‚๏ธ

IANA has registered the following value in the "OAuth Authorization Server Metadata" registry [IANA.OAuth.Params], which was established by [RFC8414].

Metadata Name:  dpop_signing_alg_values_supported

Metadata Description:  JSON array containing a list of the JWS
   algorithms supported for DPoP proof JWTs

Change Controller:  IETF

Reference:  Section 5.1 of RFC 9449

12.10. OAuth Dynamic Client Registration Metadata ๐Ÿช„

IANA has registered the following value in the IANA "OAuth Dynamic Client Registration Metadata" registry [IANA.OAuth.Params], which was established by [RFC7591].

Client Metadata Name:  dpop_bound_access_tokens

Client Metadata Description:  Boolean value specifying whether the
   client always uses DPoP for token requests

Change Controller:  IETF

Reference:  Section 5.2 of RFC 9449

13. References ๐Ÿ“š

13.1. Normative References ๐Ÿ“šโœจ

okay bestie, these are the docs this spec leans on the hardest, so they're the ones you need to have open ๐Ÿซถ

[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, <https://www.rfc-editor.org/info/rfc2119>.

[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005, <https://www.rfc-editor.org/info/rfc3986>.

[RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, DOI 10.17487/RFC5234, January 2008, <https://www.rfc-editor.org/info/rfc5234>.

[RFC6125] Saint-Andre, P. and J. Hodges, "Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)", RFC 6125, DOI 10.17487/RFC6125, March 2011, <https://www.rfc-editor.org/info/rfc6125>.

[RFC6749] Hardt, D., Ed., "The OAuth 2.0 Authorization Framework", RFC 6749, DOI 10.17487/RFC6749, October 2012, <https://www.rfc-editor.org/info/rfc6749>.

[RFC6750] Jones, M. and D. Hardt, "The OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, DOI 10.17487/RFC6750, October 2012, <https://www.rfc-editor.org/info/rfc6750>.

[RFC7515] Jones, M., Bradley, J., and N. Sakimura, "JSON Web Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May 2015, <https://www.rfc-editor.org/info/rfc7515>.

[RFC7517] Jones, M., "JSON Web Key (JWK)", RFC 7517, DOI 10.17487/RFC7517, May 2015, <https://www.rfc-editor.org/info/rfc7517>.

[RFC7519] Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015, <https://www.rfc-editor.org/info/rfc7519>.

[RFC7638] Jones, M. and N. Sakimura, "JSON Web Key (JWK) Thumbprint", RFC 7638, DOI 10.17487/RFC7638, September 2015, <https://www.rfc-editor.org/info/rfc7638>.

[RFC7800] Jones, M., Bradley, J., and H. Tschofenig, "Proof-of-Possession Key Semantics for JSON Web Tokens (JWTs)", RFC 7800, DOI 10.17487/RFC7800, April 2016, <https://www.rfc-editor.org/info/rfc7800>.

[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[SHS] National Institute of Standards and Technology, "Secure Hash Standard (SHS)", FIPS PUB 180-4, DOI 10.6028/NIST.FIPS.180-4, August 2015, <http://dx.doi.org/10.6028/NIST.FIPS.180-4>.


13.2. Informative References ๐ŸŒธ

these ones are the extra reading ๐Ÿค“ the background, the incident stories, and the registries, all the good tea that helps it all click ๐Ÿ’…

[BREACH] CVE, "CVE-2013-3587", <https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-3587>.

[Cloudbleed] Graham-Cumming, J., "Incident report on memory leak caused by Cloudflare parser bug", February 2017, <https://blog.cloudflare.com/incident-report-on-memory-leak-caused-by-cloudflare-parser-bug/>.

[CRIME] CVE, "CVE-2012-4929", <https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2012-4929>.

[GitHub.Tokens] Hanley, M., "Security alert: Attack campaign involving stolen OAuth user tokens issued to two third-party integrators", April 2022, <https://github.blog/2022-04-15-security-alert-stolen-oauth-user-tokens/>.

[Heartbleed] "CVE-2014-0160", <https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2014-0160>.

[IANA.HTTP.AuthSchemes] IANA, "Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry", <https://www.iana.org/assignments/http-authschemes/>.

[IANA.HTTP.Fields] IANA, "Hypertext Transfer Protocol (HTTP) Field Name Registry", <https://www.iana.org/assignments/http-fields/>.

[IANA.JOSE.ALGS] IANA, "JSON Web Signature and Encryption Algorithms", <https://www.iana.org/assignments/jose/>.

[IANA.JWT] IANA, "JSON Web Token Claims", <https://www.iana.org/assignments/jwt/>.

[IANA.MediaTypes] IANA, "Media Types", <https://www.iana.org/assignments/media-types/>.

[IANA.OAuth.Params] IANA, "OAuth Parameters", <https://www.iana.org/assignments/oauth-parameters/>.

[OpenID.Core] Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., and C. Mortimore, "OpenID Connect Core 1.0 incorporating errata set 1", November 2014, <https://openid.net/specs/openid-connect-core-1_0.html>.

[RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046, November 1996, <https://www.rfc-editor.org/info/rfc2046>.

[RFC4122] Leach, P., Mealling, M., and R. Salz, "A Universally Unique IDentifier (UUID) URN Namespace", RFC 4122, DOI 10.17487/RFC4122, July 2005, <https://www.rfc-editor.org/info/rfc4122>.

[RFC4949] Shirey, R., "Internet Security Glossary, Version 2", FYI 36, RFC 4949, DOI 10.17487/RFC4949, August 2007, <https://www.rfc-editor.org/info/rfc4949>.

[RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, January 2013, <https://www.rfc-editor.org/info/rfc6838>.

[RFC7523] Jones, M., Campbell, B., and C. Mortimore, "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants", RFC 7523, DOI 10.17487/RFC7523, May 2015, <https://www.rfc-editor.org/info/rfc7523>.

[RFC7591] Richer, J., Ed., Jones, M., Bradley, J., Machulak, M., and P. Hunt, "OAuth 2.0 Dynamic Client Registration Protocol", RFC 7591, DOI 10.17487/RFC7591, July 2015, <https://www.rfc-editor.org/info/rfc7591>.

[RFC7636] Sakimura, N., Ed., Bradley, J., and N. Agarwal, "Proof Key for Code Exchange by OAuth Public Clients", RFC 7636, DOI 10.17487/RFC7636, September 2015, <https://www.rfc-editor.org/info/rfc7636>.

[RFC7662] Richer, J., Ed., "OAuth 2.0 Token Introspection", RFC 7662, DOI 10.17487/RFC7662, October 2015, <https://www.rfc-editor.org/info/rfc7662>.

[RFC8414] Jones, M., Sakimura, N., and J. Bradley, "OAuth 2.0 Authorization Server Metadata", RFC 8414, DOI 10.17487/RFC8414, June 2018, <https://www.rfc-editor.org/info/rfc8414>.

[RFC8705] Campbell, B., Bradley, J., Sakimura, N., and T. Lodderstedt, "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens", RFC 8705, DOI 10.17487/RFC8705, February 2020, <https://www.rfc-editor.org/info/rfc8705>.

[RFC8707] Campbell, B., Bradley, J., and H. Tschofenig, "Resource Indicators for OAuth 2.0", RFC 8707, DOI 10.17487/RFC8707, February 2020, <https://www.rfc-editor.org/info/rfc8707>.

[RFC8725] Sheffer, Y., Hardt, D., and M. Jones, "JSON Web Token Best Current Practices", BCP 225, RFC 8725, DOI 10.17487/RFC8725, February 2020, <https://www.rfc-editor.org/info/rfc8725>.

[RFC8792] Watsen, K., Auerswald, E., Farrel, A., and Q. Wu, "Handling Long Lines in Content of Internet-Drafts and RFCs", RFC 8792, DOI 10.17487/RFC8792, June 2020, <https://www.rfc-editor.org/info/rfc8792>.

[RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, <https://www.rfc-editor.org/info/rfc9110>.

[RFC9126] Lodderstedt, T., Campbell, B., Sakimura, N., Tonge, D., and F. Skokan, "OAuth 2.0 Pushed Authorization Requests", RFC 9126, DOI 10.17487/RFC9126, September 2021, <https://www.rfc-editor.org/info/rfc9126>.

[SECURITY-TOPICS] Lodderstedt, T., Bradley, J., Labunets, A., and D. Fett, "OAuth 2.0 Security Best Current Practice", Work in Progress, Internet-Draft, draft-ietf-oauth-security-topics-23, 5 June 2023, <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-23>.

[TOKEN-BINDING] Jones, M., Campbell, B., Bradley, J., and W. Denniss, "OAuth 2.0 Token Binding", Work in Progress, Internet-Draft, draft-ietf-oauth-token-binding-08, 19 October 2018, <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-token-binding-08>.

[W3C.CSP] West, M., "Content Security Policy Level 3", W3C Working Draft, July 2023, <https://www.w3.org/TR/CSP3/>.

[W3C.WebCryptoAPI] Watson, M., "Web Cryptography API", W3C Recommendation, January 2017, <https://www.w3.org/TR/2017/REC-WebCryptoAPI-20170126>.

[WHATWG.Fetch] WHATWG, "Fetch Living Standard", July 2023, <https://fetch.spec.whatwg.org/>.


Acknowledgements ๐Ÿ’–

huge thank-you energy to everyone who helped make this happen ๐Ÿซถ Brock Allen, Annabelle Backman, Dominick Baier, Spencer Balogh, Vittorio Bertocci, Jeff Corrigan, Domingos Creado, Philippe De Ryck, Andrii Deinega, William Denniss, Vladimir Dzhuvinov, Mike Engan, Nikos Fotiou, Mark Haine, Dick Hardt, Joseph Heenan, Bjorn Hjelm, Jacob Ideskog, Jared Jennings, Benjamin Kaduk, Pieter Kasselman, Neil Madden, Rohan Mahy, Karsten Meyer zu Selhausen, Nicolas Mora, Steinar Noem, Mark Nottingham, Rob Otto, Aaron Parecki, Michael Peck, Roberto Polli, Paul Querna, Justin Richer, Joseph Salowey, Rifaat Shekh-Yusef, Filip Skokan, Dmitry Telegin, Dave Tonge, Jim Willeke, and others for their valuable input, feedback, and general support of this work ๐ŸŒˆ

this doc started out of the 4th OAuth Security Workshop in Stuttgart, Germany ๐Ÿ‡ฉ๐Ÿ‡ชโœจ big thanks to the organizers of that workshop, Ralf Kรผsters and Guido Schmitz ๐ŸŽ€


Authors' Addresses ๐Ÿฆ‹

the squad behind it all ๐Ÿ‘‘

Daniel Fett Authlete Email: mail@danielfett.de

Brian Campbell Ping Identity Email: bcampbell@pingidentity.com

John Bradley Yubico Email: ve7jtb@ve7jtb.com

Torsten Lodderstedt Tuconic Email: torsten@lodderstedt.net

Michael Jones Self-Issued Consulting Email: michael_b_jones@hotmail.com URI: https://self-issued.info/

David Waite Ping Identity Email: david@alkaline-solutions.com

๐Ÿ“ฆ where this page comes from