🤖 For AI agents: The complete documentation index is available at /docs/llms.txt. A markdown version of this page is available at /docs/lifecycle/authenticate-users/oauth/dpop.md.
Enterprise plan required

This feature requires an Enterprise plan.

Demonstrating Proof-of-Possession (DPoP) is an application-level mechanism for sender-constraining OAuth 2.0 Access and Refresh Tokens. It ensures that a token can only be used by the client that requested it, by binding the token to a cryptographic key pair held by that client.

Unlike standard bearer tokens, which can be used by any party in possession of the token, DPoP-bound tokens require the client to prove possession of a private key for every request. This provides strong defense-in-depth against token theft and replay attacks.

DPoP is defined in RFC 9449.

Enable DPoP#

The FusionAuth React, Vue, and Angular SDKs all support DPoP. To enable DPoP, set useDpop to true in your FusionAuthProviderConfig:

const fusionAuthProviderConfig: FusionAuthProviderConfig = {   redirectUri: 'http://localhost:3000',   postLogoutRedirectUri: 'http://localhost:3000',  shouldAutoRefresh: true,  scope: 'openid email profile offline_access',  clientId: 'e9fdb985-9173-4e01-9d73-ac2d60d1dc8e',  serverUrl: 'http://localhost:9011',  useDpop: true, // Opt-in to enable DPoP, defaults to false  onRedirect: () => { console.log('Login successful'); }};

Once enabled in your application, you must enable the appropriate filters for DPoP in your FusionAuth CORS configuration. In the Admin UI, navigate to the Settings -> System . Add the following settings in the CORS configuration:

  • Allowed headers: Add dpop, Authorization, and Accept, hitting Enter after each header to add each one separately
  • Allowed origins: Enter the base URL of your instance

Alternatively, you can add dpop to systemConfiguration.corsConfiguration.allowedHeaders using the API.

FusionAuth Support And Scope#

FusionAuth acts as the Authorization Server (AS) in the DPoP flow. When a client includes a DPoP proof in a token request, FusionAuth:

  1. Extracts the DPoP proof, public key, and signature from the DPoP request header.
  2. Verifies the signature using the provided public key and validates the proof according to RFC 9449 § 5.
  3. Calculates the JWK SHA-256 thumbprint (jkt) of the public key provided in the proof.
  4. Binds the issued access token (and refresh token) to this thumbprint by adding a cnf claim.
  5. Returns a token_type of DPoP in the token response.

Token Binding Semantics#

When DPoP is used, the issued access token contains a Confirmation (cnf) claim as defined in RFC 9449 § 6.1.

{
  "cnf": {
    "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
  }
}

This thumbprint is the immutable anchor that your Resource Server (RS) will use to verify that the client presenting the Token is the legitimate owner.

The DPoP Flow#

The following diagram illustrates the DPoP flow using the Authorization Code grant with PKCE.

API (RS)FusionAuth (AS)AppClient/BrowserAPI (RS)FusionAuth (AS)AppClient/BrowserUser Logs InGenerate cryptographic Key PairStart DPoP Token IssuanceConstruct & Sign DPoP Proof 1Extract Proof 1, Public Key & SignatureAccess Protected ResourceConstruct & Sign DPoP Proof 2Extract Proof 2, Public Key & SignatureConstruct & Sign DPoP Proof 3Extract Proof 3, Public Key & SignatureAccess Token ExpiresStart DPoP Token RefreshConstruct & Sign DPoP Proof 4Extract Proof 4, Public Key & SignatureView Initial PageClick LoginRedirect User To Authorization Server With ScopesRequest Login PageReturn Login PageProvides CredentialsValidate CredentialsRedirect With Authorization CodeRequest Redirect URIRequest TokensDPoP Header (Proof 1)Verify Proof 1 Signature & Claims​Bind Token to Public Key Thumbprint (jkt)​Return sender-constrainedAccess & Refresh TokensStore Tokens(e.g., in memory or secure storage)API Request (Access Token)DPoP Header (Proof 2)Verify Access Token & BindingVerify Proof 2 Signature & Claims​API ResponseAPI Request (Access Token)DPoP Header (Proof 3)Verify Access Token & BindingVerify Proof 3 Signature & Claims​API ResponseRefresh Token RequestDPoP Header (Proof 4)Verify Refresh Token & BindingVerify Proof 4 Signature & Claims​Bind Token to Public Key Thumbprint (jkt)​New Access Token Response

Key Generation And Authorization#

  1. Key Generation: The client generates a cryptographic public and private key pair (e.g., ES256) on the browser. Generating the key pair at the start of the flow ensures the client is ready to sign proofs later.
  2. Authorization Request: The client initiates the OAuth flow by redirecting the user to FusionAuth's authorization endpoint.

DPoP Token Issuance (Authorization Server)#

  1. Token Request: After the user authenticates and authorizes the client, the client requests Tokens from the /oauth2/token endpoint, including a DPoP proof (Proof 1) signed with the private key in the DPoP header.
  2. Verification (AS): FusionAuth extracts the DPoP proof and public key, verifies the signature, and ensures the claims (like htm and htu) are valid.
  3. Binding (AS): FusionAuth then generates an Access Token bound to the SHA-256 thumbprint (jkt) of the public key.
  4. Sender-Constrained Issuance (AS): FusionAuth issues the Tokens with a token_type of DPoP, ensuring they are constrained to the client's key.

Access Protected Resource (Resource Server)#

  1. API Request (Client): For every API call, the client generates a new DPoP proof (Proof 2) specific to the request (matching HTTP method htm and URI htu) and includes the Access Token hash (ath).
  2. Verification (RS): Your API (Resource Server) verifies the Access Token, validates the DPoP proof signature, and ensures the proof's key matches the cnf.jkt binding in the Token.
  3. Response (RS): Your API responds with the requested resource. Subsequent requests (e.g., Proof 3) follow the same pattern.

DPoP Token Refresh (Authorization Server)#

  1. Refresh Request (Client): When the Access Token expires, the client sends a Refresh Token request to the /oauth2/token endpoint, including a new DPoP proof (Proof 4).
  2. Verification & Re-binding (AS): FusionAuth verifies the Refresh Token and its binding to the original key, validates the new DPoP proof, and issues a new Access Token bound to the same public key thumbprint.

Client Responsibilities#

Key Pair Lifecycle#

  • Generation: The client must generate an asymmetric key pair. RFC 9449 § 4.2 recommends using algorithms like ES256. Generating the key early ensures the client is ready for the token request and can fail fast if the browser does not support the required cryptographic algorithms.
  • Storage: In browser environments, store the private key in a way that it is non-extractable (e.g., using the Web Crypto API with extractable: false). This prevents exfiltration even if the application context is compromised by XSS. RFC 9449 § 11.4.
  • Rotation: Periodically rotate the key pair to minimize the impact of a potential key compromise.

DPoP Proof Composition#

A DPoP proof is a JWT sent in the DPoP HTTP header. According to RFC 9449 § 4.2, it must contain:

JOSE Header:

  • typ: MUST be dpop+jwt.
  • alg: An asymmetric signature algorithm (e.g., ES256). FusionAuth accepts the following algorithms: Ed448, Ed25519, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, and PS512.
  • jwk: The public key corresponding to the private key used to sign the proof.

Payload Claims:

  • jti: A unique identifier for the proof (UUID v4 is recommended) to prevent replay.
  • htm: The HTTP method of the request (e.g., GET, POST).
  • htu: The HTTP target URI of the request, without query or fragment parameters.
  • iat: The time the proof was created. FusionAuth allows a lifetime of 10 seconds and a window of +/- 15 seconds to account for clock skew.
  • ath: The base64url-encoded SHA-256 hash of the Access Token (required when presenting an Access Token to an RS).

Resource Server Validation Checklist#

Your API (Resource Server) must perform the following steps to validate a DPoP-protected request as outlined in RFC 9449 § 4.3:

  1. Verify Headers: Ensure the Authorization header uses the DPoP scheme and the DPoP header is present and is a valid JWT.
  2. Strict Type Check: Verify that the DPoP proof's JOSE header has typ: "dpop+jwt".
  3. Signature Verification: Verify the proof's signature using the public key provided in the jwk header of the proof itself.
  4. Claims Validation:
    • htm: Matches the request's HTTP method.
    • htu: Matches the request's absolute URI (normalization is recommended).
    • iat: Within an acceptable window (e.g., +/- 15 seconds) to account for clock skew.
    • jti: Has not been seen before (replay protection).
  5. Access Token Hash (ath): Verify that the ath claim in the proof matches the SHA-256 hash of the Access Token provided in the Authorization header.
  6. Token Binding Check:
    • Extract the cnf.jkt claim from the Access Token.
    • Calculate the SHA-256 thumbprint of the jwk from the DPoP proof.
    • MUST ensure they are identical.

FusionAuth Configuration#

There is no configuration required to enable DPoP in FusionAuth. FusionAuth responds to Token requests with a DPoP header containing a DPoP proof as long as the requesting client initializes the DPoP flow.

Troubleshooting#

ErrorCauseResolution
invalid_dpop_proofThe DPoP proof is malformed, has an invalid signature, or missing claims.Verify the client is correctly signing the JWT and including all required claims (§4.2).
use_dpop_nonceThe server requires a fresh nonce.Extract the DPoP-Nonce from the response and retry the request with the nonce claim.
Thumbprint MismatchThe jkt in the Access Token does not match the key in the DPoP proof.Ensure the client is using the same key pair for the API request as it did for the Token request.
ath MismatchThe hash of the Access Token in the proof is incorrect.Verify the ath calculation: base64url(sha256(Access Token)).

Storage Options#

Here is a complete list of storage options for Access and Refresh Tokens in comparison.

OptionStrengthsWeaknessesSecurity ConsiderationsRecommendedSupported By FusionAuth
Secure, HTTPOnly cookiesTokens sent automatically when credentials option sent, widely supportedWon't work if clients are not on same domain as the APIs, requires a browser, APIs must look for access token in cookie header, not in other placesReduces attack surface by preventing JavaScript from accessing tokens, XSS resistant, but requires correct cookie configuration and same-domain policiesYesYes
Backend for frontend (BFF)/SessionsEasy to revoke tokens, works with any client, can be combined with cookie approach to provide cross domain access if some APIs live on different domainsAdditional server side component, less scalable because you are routing all API requests through the BFF, single point of failure for all API accessMinimizes token exposure by never sharing tokens with the client, but introduces a new critical infrastructure component that must be securedYesYes
Native secure storageOS-provided secure storage options like iOS Keychain or Android Keystore prevent token exfiltrationPlatform-specific implementationPrevents exfiltration by malicious apps, use TLS to prevent attacker-in-the-middle attacksYesYes
Mutual-TLS (MTLS)Tokens are bound to client, IETF standardNot widely supported, requires every client to have an X.509 certificateOffers strong token-binding security, but operational complexity and certificate management introduce significant overheadYes (when available)No; tracking issue
Distributed Proof of Possession (DPoP)Tokens are bound to client, IETF standardRequires per-request proof generation by the client and verification by APIs; clients must manage and protect a private key (non-extractable, rotation); APIs may need nonce/replay handlingEnhances token security by binding it to a client, but susceptible to implementation flaws and requires stringent key managementYesYes, Please review our DPoP documentation for tradeoffs and integration guidance.
Local Storage, IndexedDBJavaScript can access token and send requests using Authorization or other expected header, supported by some frameworks (Amplify)Tokens vulnerable to exfiltration by any JavaScript running on the pageHighly vulnerable to XSS attacksNoYes, but you have to write your own backend to deliver the token
In MemoryYour code can access tokens but other code cannotTokens lost when the client reloadsReduces persistence of tokens, limiting exposureNoYes, but you have to write your own backend to deliver the token
Web workersBrowser APIs provide a layer of isolation between the web worker and the access tokenAll fetch calls must pass through web worker, have to use a library or write web worker integration code, malicious code may still be able to get data via the web worker, access token is removed when page is reloadedProvides isolation for tokens, but risks persist with malicious code targeting the web worker or intercepting network trafficNoYes, but you have to write your own backend to deliver the token and front end JavaScript to store