Demonstrating Proof of Possession (DPoP): OAuth2 security for FAPI 2.0 and open banking

 

Imagine a concert ticket: whoever holds it gets into the concert. It doesn’t matter who bought it, or how they got it – if you can show the ticket at the entrance, you’re in. What’s more, you could pass the ticket to your friends behind you in the queue, and they would be able to get in too.

That’s essentially how OAuth2 bearer tokens work. Once a token is issued, any party that presents it can access protected resources. The simplicity is convenient, but makes bearer tokens easy to steal and replay. If an attacker intercepts one through an insecure app, exposed network, or logging error, they could impersonate the client’s access until the token expires.

RFC 9449 defines a way to bind access tokens to a cryptographic key instead of just trusting whoever bears the token.

We call this Demonstrating Proof of Possession (DPoP) – and it’s a huge step forward in securing APIs, especially in open, mobile, and browser-based environments.

Key DPoP benefits include:

  • Preventing token theft: A stolen token alone is useless without the client’s private key. Even if an attacker obtains the token, it’s impossible to forge the matching DPoP proof.
  • Public clients and mobile apps: Unlike mTLS (which requires certificates and is hard to achieve in SPAs/mobile), DPoP works entirely at the application layer. Clients simply store a non-extractable key (via Web Crypto) and sign proofs so that mobile or SPAs can safely use OAuth without exposing keys.
  • Replay protection: Each DPoP proof JWT contains a globally unique ID and specifies the HTTP method and URL. This enables servers to detect whether the proofs are being reused or mixed and matched across different requests.

Don’t worry if this sounds complicated – it is – but we will take you through each of these benefits or protections throughout the post.

Going back to our concert ticket analogy, it has a date and a venue. This means it is not valid for reuse across the entire tour. And imagine that your name and date of birth were also printed on it, and you had to show your passport or driver’s license at the door to prove that the ticket is really yours. 

In this post, we’ll explain how DPoP works – both the core ideas behind it and how to use it in practice. We’ll look at its utility across industries and explain how API platforms like Tyk adapt to support FAPI 2.0-ready ecosystems.

The problem with bearer tokens

Bearer tokens are easy to use—anyone who has the token can access the associated resource.

However, this simplicity comes with significant underlying security risks. 

  • Token theft: If a token is leaked via application logs, browser storage, or insecure APIs, an attacker can impersonate the user.
  • Replay attacks: Tokens can be reused multiple times unless explicitly scoped and expired quickly.
  • Vulnerable client environments: Public clients like browsers and mobile apps can’t securely store secrets, making bearer tokens especially vulnerable in these contexts.

OAuth2 doesn’t have a built-in way to ensure that only the original client can use the token. Once issued, anyone with the token can use it. This may be acceptable for low-value or short-lived tokens. But for anything sensitive – like personal data, payment initiation, healthcare APIs – it’s a problem.

Enter DPoP: Binding tokens to clients

DPoP enhances OAuth2 by binding the access token to a cryptographic key. Instead of trusting the bearer, the system now trusts the holder of a private key.

 

Feature

Bearer Token

DPoP Token + DPoP Proof

Bound to Client

NO

YES

Replay Protection

NO

YES (nonce + URL)

Works with SPAs/Mobile

YES (but insecure)

YES

Requires TLS Client Certificates

NO

NO

DPoP offers the security of mTLS, with the added simplicity of Bearer tokens.

But what about mTLS?

Mutual TLS is the original gold standard for sender-constrained tokens, where both client and server validate each other’s certificates. It’s incredibly secure, but also operationally heavy.

  • It requires issuing and managing client certificates
  • It is impossible for public clients, mobile apps and SPAs to implement, as they cannot store secrets securely.
  • It introduces significant infrastructure complexity, such as termination at edge proxies and TLS renegotiation.

In contrast, DPoP achieves similar guarantees at the application layer using request headers, without all the friction. It’s ideal for public, and semi-trusted clients, that cannot store or rotate X.509 certificates.

FAPI 2.0 allows both mTLS and DPoP as valid token binding methods. DPoP is often preferred for public clients, SPAs, and mobile apps where TLS certificates are not practical.

How DPoP works (step-by-step)

Here’s a quick sequence diagram which walks through the DPoP flow step by step.

 

  • Client generates a key pair (asymmetric, e.g., EC P-256).

  • Client generates an initial DPoP proof, embedding the public key in the JWT header.

  • Client requests an access token, sending the DPoP JWT in the DPoP header.

  • Authorization server issues a DPoP-bound access token, including a fingerprint of the key embedded within a cnf.jkt claim.

  • For each API request, the client:
    – Generates a fresh DPoP proof with a unique jti, htm, and htu.
    – Sends the DPoP proof alongside the Authorization: DPoP <token> header.

  • The resource server verifies:
    – The DPoP JWT is signed by the correct private key.
    – The token’s key binding matches the key in the DPoP JWT.
    – The request URL (htu) and method (htm) match the DPoP proof.
    – The nonce (jti) hasn’t been replayed.

A real-world DPoP flow

Let’s look at a practical example. Assume you have a JavaScript-based public client (e.g., a single-page app or mobile app) using Keycloak as your authorization server.

Step 1 – Generate a DPoP key-pair client-side using WebCrypto or equivalent.

 

const keyPair = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,
  ["sign", "verify"]
);

 

Step 2 – Create the DPoP JWT

 

{
  "htu": "https://auth.example.com/token",
  "htm": "POST",
  "iat": 1680000000,
  "jti": "unique-uuid"
}


This JWT needs to be signed using the private key from step 1, and the public key provided in the JWT header as a JWK.

Step 3: Request an access token.

In a public client (like a browser SPA), client credentials grant is not permitted.

Instead, use Authorization Code flow with PKCE. For brevity, we are skipping ahead to the part where we are exchanging the authorization code for the access token.

 

fetch("https://auth.example.com/token", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    "DPoP": "<signed JWT>"
  },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: "<auth_code>",
    redirect_uri: "https://your-app/callback",
    client_id: "your-client-id",
    code_verifier: "<your-code-verifier>"
  })
})

 

Step 4: Call the protected resource.

 

const dpopProof = await generateDPoPProof({
  htu: "https://api.example.com/resource",
  htm: "GET",
  privateKey,
  kid
});
fetch("https://api.example.com/resource", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <access_token>",
    "DPoP": dpopProof
  }
})

Why this matters for API platforms

DPoP isn’t just a nice-to-have…

Banking and Fintech (Open Banking / PSD2)

DPoP and FAPI 2.0 are especially relevant in banking and fintech, where open banking initiatives mandate strong API security. In the EU’s PSD2 (Payment Services Directive) and UK Open Banking, APIs for account and payment data must implement “Financial-Grade” OAuth profiles. In fact, FAPI is now the basis for almost all open banking and open finance standards around the worldincluding the UK (OBIE), Brazil Open Finance, and Australia’s CDR.

FAPI 1.0 Advanced (used by UK OBIE and Brazil) mandated mTLS (certificate-bound tokens). FAPI 2.0 now allows DPoP as an alternative. This gives banks and fintechs the flexibility to phase out complex client certificates and adopt simpler, application-layer binding. Open banking regimes like OBIE and Brazil’s OBB are updating toward FAPI 2.0, making DPoP support increasingly common.

These features align with regulatory expectations around strong customer authentication and consent management. By adopting DPoP, banks and fintechs can better protect against token replay and theft in high-risk environments like mobile banking and third-party aggregators.

Healthcare APIs

Healthcare data is highly sensitive and subject to regulations like HIPAA and GDPR. Modern health APIs (e.g., SMART on FHIR) increasingly use OAuth2/OIDC. DPoP provides extra assurance by binding tokens to specific app instances. If a token is leaked (via device compromise or XSS), it can’t be reused elsewhere. DPoP isn’t mandated in healthcare yet, but it’s a strong fit for these kinds of APIs.

Government and public sector

Governments are also adopting high-assurance API standards. The OpenID Foundation notes that FAPI is now a nationwide standard in many regions. National open data and digital identity initiatives are increasingly aligning with FAPI. DPoP ensures only verified apps can use issued tokens—crucial for citizen identity, benefits, and voting APIs. It’s also well-suited to SPAs and mobile clients, which are common in government apps.

Conclusion and next steps

Bearer tokens are convenient – but risky. Demonstrating Proof of Possession offers a better way. By binding tokens to keys and verifying proof on each request, DPoP strengthens API security without sacrificing usability.

We encourage you to:

  • Explore if you would benefit from DPoP in your own OAuth flows
  • Try our plugin-based approach to DPoP proof validation
  • Share your feedback if native DPoP support in Tyk matters to you

References

RFC 9449 – OAuth DPoP

FAPI 2.0 – OpenID Foundation

Tyk DPoP Plugin