# Demonstrating Proof-of-Possession (DPoP)

Learn how DPoP binds FusionAuth access and refresh tokens to a key pair that only your client holds.

> For the index of this section of the site, see [llms.txt](https://fusionauth.io/docs/llms.txt)

> **PLAN:** This feature requires [an Enterprise plan](https://fusionauth.io/docs/concepts/plans-and-limits/plans-features.md#enterprise-features).

Available since `1.63.0`

FusionAuth issues bearer tokens by default. Anyone who holds a bearer token can use the token. Demonstrating Proof-of-Possession (DPoP) binds a token to a key pair that your client generates. To use a bound token, the client signs a short-lived JWT called a DPoP proof and sends the proof with every request. An attacker who steals a bound token can't use the token without the private key.

FusionAuth implements DPoP as defined in [RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449). FusionAuth acts as the authorization server: FusionAuth checks the proof on token requests and issues bound tokens. Your API acts as the resource server and checks the proof on each request.

To add DPoP to a single-page app, see [Use DPoP In A Browser App](https://fusionauth.io/docs/authentication/oauth/dpop/browser-app.md). To check DPoP proofs in your API, see [Validate DPoP Requests In Your API](https://fusionauth.io/docs/authentication/oauth/dpop/validate-requests.md).

## How DPoP Works

The client creates a key pair before the first token request. Each request then carries a new proof signed with the private key. The proof names the HTTP method and URL of the request, so a proof captured from one request doesn't work for another. Requests that carry an access token also include a hash of that access token in the proof.

```mermaid
sequenceDiagram
    participant Client
    participant FusionAuth
    participant API
    Note over Client: Generate a key pair
    Client ->> FusionAuth : Token request with a DPoP proof
    Note over FusionAuth: Verify the proof<br/>Compute the key thumbprint
    FusionAuth ->> Client : Access token with cnf.jkt<br/>token_type DPoP
    Client ->> API : Authorization: DPoP <access token><br/>DPoP: <new proof>
    Note over API: Verify the proof<br/>Match the key to cnf.jkt
    API ->> Client : Response
```

The client binds tokens to a key at the token endpoint, then proves possession of the key on each API request.

FusionAuth checks the proof signature against the public key in the proof header. FusionAuth then computes the JWK SHA-256 thumbprint of the public key and writes the thumbprint into the access token. Your API compares the thumbprint in the access token with the key in each new proof. A request signed with a different key fails.

## Turn On DPoP

FusionAuth has no DPoP setting. A client opts in by sending a **DPoP** header on a token request. When the request carries a valid proof, FusionAuth binds the tokens. When the request has no proof, FusionAuth issues bearer tokens.

You can't require DPoP for an Application. Without an Enterprise license, a token request that includes a proof fails with a `not_licensed` error.

## What FusionAuth Binds

When a token request includes a valid proof, FusionAuth binds the tokens in the response:

*   The access token gets a **`cnf`** claim. The **`jkt`** value is the thumbprint of the public key in the proof.
*   The token response returns `DPoP` as the **`token_type`**.
*   FusionAuth binds the refresh token to the same thumbprint. FusionAuth stores the binding with the refresh token, because a refresh token isn't a JWT.

FusionAuth doesn't bind Id tokens.

A bound access token contains a **`cnf`** claim like the following:

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

The [Introspect endpoint](https://fusionauth.io/docs/apis/oauth/introspect.md) also returns the **`cnf`** claim for bound tokens.

### Bind The Authorization Code

By default, FusionAuth binds tokens at the token request. To bind the authorization code too, send the thumbprint of your public key in the `dpop_jkt` parameter of the [Authorize endpoint](https://fusionauth.io/docs/apis/oauth/authorize.md). FusionAuth then requires a proof from the same key when the client exchanges the code.

## Supported Grants

FusionAuth accepts DPoP proofs on the `/oauth2/token` endpoint for the following grants:

*   Authorization code
*   Refresh token
*   Client credentials
*   Password
*   Device code

For the device code grant, send the proof to `/oauth2/device_authorize`. FusionAuth binds the device code to the key, and the token request must carry a proof from the same key. A token request with a proof for a device code issued without a proof fails. Support for DPoP with the device code grant follows a [draft specification](https://datatracker.ietf.org/doc/draft-parecki-oauth-dpop-device-flow/).

The token exchange grant doesn't bind tokens. The token exchange grant also rejects a bound subject token. See [Token Exchange](https://fusionauth.io/docs/authorization/token-exchange.md).

## Where FusionAuth Accepts A Bound Token

FusionAuth accepts a bound access token with the `DPoP` scheme and a new proof in the following places:

*   The `/oauth2/userinfo` endpoint.
*   FusionAuth APIs that support [JWT authentication](https://fusionauth.io/docs/apis/authentication.md#jwt-authentication).
*   The [Issue A JWT](https://fusionauth.io/docs/apis/jwt/issue-a-jwt.md) and [Refresh A JWT](https://fusionauth.io/docs/apis/jwt/refresh-a-jwt.md) APIs. Tokens from these APIs are bound to the key in the proof.

FusionAuth rejects a bound access token sent any other way, including with the `Bearer` or `JWT` scheme or in the `access_token` cookie. The rejection stops an attacker from downgrading a stolen bound token to a bearer token.

FusionAuth applies the same rule to refresh tokens. A bound refresh token requires a proof from the same key. An unbound refresh token rejects any proof, so a client can't bind a bearer session to a new key.

## Proof Requirements

FusionAuth accepts proofs signed with the following algorithms: `Ed448`, `Ed25519`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512`, `RS256`, `RS384`, and `RS512`. The [OpenID Connect discovery document](https://fusionauth.io/docs/apis/oauth/openid-configuration.md) lists the same algorithms in **`dpop_signing_alg_values_supported`**.

FusionAuth accepts a proof for 10 seconds after the proof's **`iat`** time, plus 15 seconds of clock skew in either direction. For every claim a proof contains, see [Proof Claims](https://fusionauth.io/docs/authentication/oauth/dpop/validate-requests.md#proof-claims).

## Limitations

FusionAuth doesn't support DPoP nonces. FusionAuth never returns a **DPoP-Nonce** header, and FusionAuth ignores the **`nonce`** claim.

FusionAuth checks that each proof has a **`jti`** claim, but FusionAuth doesn't record **`jti`** values. FusionAuth relies on the short proof lifetime to limit replay. To reject replayed proofs at your API, store **`jti`** values in your API. See [Validate DPoP Requests In Your API](https://fusionauth.io/docs/authentication/oauth/dpop/validate-requests.md).

DPoP protects a token after theft. DPoP doesn't protect against code running in the user's browser, because that code can ask the client to sign proofs. To compare DPoP with other ways to store tokens in a browser, see [Token Storage](https://fusionauth.io/docs/security/tokens-and-keys/token-storage.md).

[Use In A Browser App Learn how to bind tokens to the browser with DPoP using the FusionAuth React, Angular, or Vue SDK.](https://fusionauth.io/docs/authentication/oauth/dpop/browser-app.md)

[Validate In Your API Learn how to check DPoP proofs and DPoP-bound FusionAuth access tokens in your API.](https://fusionauth.io/docs/authentication/oauth/dpop/validate-requests.md)

Related

[Authentication Validate DPoP Requests In Your API Learn how to check DPoP proofs and DPoP-bound FusionAuth access tokens in your API. dpopproof of possessionsender-constrained tokens](https://fusionauth.io/docs/authentication/oauth/dpop/validate-requests.md)

[Authentication Use DPoP In A Browser App Learn how to bind tokens to the browser with DPoP using the FusionAuth React, Angular, or Vue SDK. dpopproof of possessionsender-constrained tokens](https://fusionauth.io/docs/authentication/oauth/dpop/browser-app.md)

[APIs JWTs & Refresh Token API Learn about the APIs that allow you to manage Refresh Tokens, verify Access Tokens and retrieve public keys used for verifying JWT signatures. access tokenrefresh token](https://fusionauth.io/docs/apis/jwt.md)

[APIs Token API API documentation for the FusionAuth Token OAuth2 endpoint. oauthrefresh token](https://fusionauth.io/docs/apis/oauth/token.md)
---

## Other pages in OAuth

> For the full index of this section, see [Authentication](https://fusionauth.io/docs/llms-authentication.txt).

- [CIMD Registration Caching And Lifecycle](https://fusionauth.io/docs/authentication/oauth/cimd-registration-lifecycle.md): Learn how long FusionAuth caches a Client ID Metadata Document, what happens when it expires, and how a CIMD client registration is refreshed or removed.
- [OAuth Client Registration | Static and CIMD](https://fusionauth.io/docs/authentication/oauth/client-registration.md): Learn how FusionAuth registers OAuth clients, either ahead of time as Applications or just-in-time from a Client ID Metadata Document.
- [Use DPoP In A Browser App](https://fusionauth.io/docs/authentication/oauth/dpop/browser-app.md): Learn how to bind tokens to the browser with DPoP using the FusionAuth React, Angular, or Vue SDK.
- [Validate DPoP Requests In Your API](https://fusionauth.io/docs/authentication/oauth/dpop/validate-requests.md): Learn how to check DPoP proofs and DPoP-bound FusionAuth access tokens in your API.
- [Host A Client ID Metadata Document (CIMD)](https://fusionauth.io/docs/authentication/oauth/host-cimd-document.md): Publish a Client ID Metadata Document so your OAuth client can log in to FusionAuth without being registered ahead of time.
- [OAuth](https://fusionauth.io/docs/authentication/oauth.md): Learn about the OAuth 2.0 and OpenID Connect grants FusionAuth supports for login and single sign-on.
- [OAuth Issuer Validation](https://fusionauth.io/docs/authentication/oauth/issuer-validation.md): Learn how to validate the authorization response issuer parameter (RFC 9207) to prevent mix-up attacks.
- [Connect claude.ai And chatgpt.com To A FusionAuth-Protected MCP Server](https://fusionauth.io/docs/authentication/oauth/mcp-clients-claude-chatgpt.md): Learn how to connect claude.ai and chatgpt.com to an MCP server protected by FusionAuth using Client ID Metadata Documents.
- [Modes](https://fusionauth.io/docs/authentication/oauth/modes.md): Learn about the eight common ways apps use OAuth and OpenID Connect.
- [OIDC Prompt](https://fusionauth.io/docs/authentication/oauth/prompt.md): Learn about OpenID Connect prompt and example use cases.
- [OAuth Response Modes](https://fusionauth.io/docs/authentication/oauth/response-modes.md): Learn about OAuth 2.0 response modes (query, fragment, form_post) and when to use each one.
- [Manage Software Tokens](https://fusionauth.io/docs/authentication/oauth/tokens.md): Learn about OAuth2 and OpenID Connect Tokens and how they are used.
- [URL Validation](https://fusionauth.io/docs/authentication/oauth/url-validation.md): Learn about OAuth URL validation policies in FusionAuth.
