Keymate Logo
← Back to Blog

Beyond Bearer Tokens: Implementing DPoP for Modern Enterprise Identity

Keymate Team
April 2026
Beyond Bearer Tokens: Implementing DPoP for Modern Enterprise Identity

Presented at Keycloak DevDay 2026. Built into the Keymate stack.

Estimated read: 7–8 minutes

TL;DR

  • Bearer tokens are cash. Whoever holds one can spend it. The server cannot tell the difference between the real client and an attacker who stole the token.
  • DPoP (RFC 9449) binds every access token to a cryptographic key pair and requires a fresh, signed proof on every request. Stolen token without the private key? Useless.
  • Keymate implements DPoP end-to-end. Proof generation in our Admin Console and JS SDK, validation in our Access Gateway and open-source APISIX plugin.
  • Keycloak makes adoption practical with per-client toggles and graceful fallback. But resource-server validation, replay caching, and client-side proof generation are gaps we filled ourselves.

The Problem: Bearer Tokens Are Cash

A bearer token works like a banknote. The API server honors whoever presents it, no questions asked. If an attacker gets your token through XSS, log leakage, or a network interception, they get full authorized access from any device. The server has no way to tell the real holder from the thief.

Three things make this dangerous:

  1. Token exfiltration is common. Tokens end up in server logs, error tracking tools, browser extensions, and compromised CDN scripts.
  2. No sender constraint. The token carries no proof of who is presenting it.
  3. No replay protection. A captured token (or request) can be reused indefinitely until it expires.

Bearer tokens were designed for simplicity. That simplicity became a problem.

Diagram showing how a stolen bearer token grants an attacker full access because the API server cannot distinguish the legitimate client from the thief

The Solution: DPoP in 60 Seconds

DPoP stands for Demonstrating Proof-of-Possession (RFC 9449). It adds three layers of protection that bearer tokens lack.

Cryptographic Binding

The client generates an ES256 key pair. During token issuance, the authorization server (Keycloak) computes the SHA-256 thumbprint of the public key and embeds it inside the access token as a cnf.jkt claim. From that moment, the token is bound to that specific key pair. A stolen token is useless without the matching private key.

Replay Protection

On every request, the client signs a short-lived proof JWT that includes a unique identifier (jti) and a timestamp (iat). The resource server caches each jti and rejects duplicates. An intercepted proof cannot be replayed.

Endpoint Binding

Each proof also includes the HTTP method (htm) and the target URL (htu). A proof created for GET /api/users cannot be used for POST /api/admin. A captured proof is scoped to exactly one endpoint and one moment in time.

How DPoP Compares to Bearer Tokens

Relying on bearer tokens creates a security debt: a single compromised log or XSS vulnerability can bypass months of hardening. DPoP changes the model by shifting from bearer to proof-of-possession. For teams building modern enterprise identity or targeting FAPI 2.0 compliance, this is the foundation.

Feature Bearer Tokens DPoP Tokens
Security Model Bearer (no proof) Proof-of-Possession
Theft Risk High (usable by anyone) Low (sender-constrained)
Complexity Low Medium
Compliance OAuth 2.0 Baseline FAPI 2.0 / High-Security

Bearer tokens are cash. DPoP tokens are credit cards with a PIN and per-transaction limits.

How Keycloak Makes DPoP Possible

Keycloak (26.4+) has solid authorization-server support for DPoP:

  • Per-client toggle. A single checkbox, "Require DPoP bound tokens", enables DPoP for a specific client. No code changes.
  • Graceful fallback. Keycloak accepts DPoP proofs even when the toggle is off. This lets you migrate gradually.
  • Automatic binding. Keycloak computes and embeds the cnf.jkt claim during token issuance.
  • Native API protection. The Admin REST API and Account API validate DPoP proofs out of the box.
  • Public client support. SPAs and mobile apps can opt into refresh-token-only binding when full DPoP is not feasible.

What's Left for You to Build

Keycloak covers the authorization server side. For your own APIs, microservices, and gateways, you need to handle the resource server side yourself:

  • Resource server validation. You need to verify proofs, match cnf.jkt, and enforce htm/htu binding yourself.
  • Replay protection at scale. jti caching across distributed pods requires a multi-layer cache strategy.
  • Client-side proof generation. There is no official Keycloak JS library for signing DPoP proofs.

These are the gaps we filled.

How Keymate Implements DPoP End-to-End

Admin Console: Server-Side (BFF)

Our Admin Console uses a Backend-for-Frontend architecture. The browser communicates with the BFF via session cookies. The BFF communicates with Keycloak and downstream APIs using DPoP-bound tokens.

This setup is ideal for DPoP because the private key never leaves the server. It gets generated during login, serialized to JWK format, and stored in a Redis session alongside the access token. On every API call, the BFF reads the key and token in a single Redis round-trip, generates a fresh proof, and attaches it to the outgoing request.

A DPOP_ENABLED feature flag controls the entire flow. Set it to false and everything reverts to bearer tokens. No code changes, no redeployment. We used this to roll out DPoP incrementally across environments.

Error handling follows the RFC closely: if the resource server returns an invalid_dpop_proof error, the interceptor generates a fresh proof and retries the request once. If DPoP keys are missing from the session for any reason, the system gracefully falls back to bearer.

JS SDK: Client-Side Proof Generation

Our JS SDK (@keymate/access-sdk-js) provides a DPoPManager that handles proof generation for any JavaScript runtime:

  • generateKeyPair(extractable?) creates an ES256 key pair via the Web Crypto API. Pass extractable: true for server-side use (keys can be serialized to JWK for Redis). Pass extractable: false for browsers, where the private key becomes a non-extractable CryptoKey handle that JavaScript cannot read, only use for signing.
  • generateProof(method, url, accessToken, keyPair) produces a standards-compliant DPoP proof JWT with jti, iat, htm, htu, and ath claims.
  • Automatic interceptor. When a token is set with type: 'DPoP', the SDK's HTTP client generates and attaches a fresh proof to every outgoing request. No manual wiring needed.

The SDK works in browsers, Node.js, Deno, Bun, and Cloudflare Workers. Anywhere the Web Crypto API is available.

Access Gateway: DPoP Validation for Our Services

Diagram showing the Keymate Admin Console sending API requests with DPoP proofs to the Access Gateway, which validates proofs via Keycloak introspection and Infinispan JTI cache before proxying to target APIs

Our Access Gateway validates every DPoP proof from the Admin Console before the request reaches backend services (Keycloak Admin REST API, microservices).

The flow: the gateway triggers a token introspection call to Keycloak, extracts the cnf.jkt from the response, matches it against the proof's key thumbprint, and checks the jti against a remote Infinispan cache for replay protection. If anything fails, the request gets a 401.

This is how we protect our own services. But we also wanted to make DPoP validation available to everyone.

APISIX Plugin: Open-Source DPoP for the Community

Diagram showing a browser or SPA sending API requests with DPoP proofs to the APISIX Gateway, which validates proofs, checks JTI against L1/L2 cache, strips the DPoP header, rewrites to Bearer, and proxies to backend APIs

We built an open-source APISIX plugin so any team can add DPoP validation to their API gateway. The plugin:

  1. Parses the DPoP proof and access token from incoming requests.
  2. Obtains the cnf.jkt, either from local JWT verification or via Keycloak token introspection.
  3. Matches the proof's key thumbprint against the token's cnf.jkt.
  4. Checks the jti against an L1/L2 hybrid cache (in-memory + Redis/Infinispan) to prevent replay.
  5. Strips the DPoP header and rewrites the request to Bearer before proxying upstream.

Your backend services receive a normal bearer token and require zero changes. DPoP validation happens entirely at the edge.

DPoP for SPAs: Where Do the Keys Live?

In a BFF architecture, keys live in a server-side session. Problem solved. But what about single-page applications that talk directly to APIs?

SPAs need to store the key pair in the browser. The recommended approach:

IndexedDB + non-extractable CryptoKey. Generate keys with extractable: false. The Web Crypto API stores the private key as an opaque handle. JavaScript can use it for signing but cannot read the raw key material. Store the CryptoKey object in IndexedDB for persistence across page reloads.

Alternatives exist but come with trade-offs. sessionStorage loses keys on tab close and exposes them to XSS. In-memory storage is the most secure against persistence attacks but loses keys on every page refresh.

DPoP does not eliminate XSS. If an attacker can execute JavaScript in your page, they can use the CryptoKey handle to sign proofs. But they cannot extract the private key for use on another device. DPoP raises the bar from "steal a token string" to "maintain persistent code execution in the victim's browser session."

What We're Proposing to the Keycloak Community

We presented three proposals at Keycloak DevDay 2026 to make DPoP adoption easier for everyone:

1. Reference Resource Server Validation Library. Keycloak handles the authorization server side well. But every team building custom APIs has to implement proof validation from scratch. A reference library (Java/JS) from the Keycloak ecosystem would lower the barrier a lot.

2. Case-Insensitive DPoP Handling. Some Keycloak code paths treat the DPoP auth-scheme as case-sensitive. RFC conventions say auth-schemes should be case-insensitive. We proposed standardizing this in DPoPUtil and the introspection provider.

3. DPoP-Aware Token Exchange (RFC 8693). When a subject token is DPoP-bound, the exchange request should require a matching DPoP proof and verify that the proof's key thumbprint matches the token's cnf.jkt, rather than rejecting the request outright. Keycloak 26.6 will block token exchange for DPoP-bound tokens. We proposed a validation-based approach where presenter binding survives token exchange flows instead of breaking them.

We think DPoP should be a first-class concern in the OAuth ecosystem, not something each team has to figure out on their own.

References


Keymate builds identity infrastructure that goes beyond authentication. If you are looking into sender-constrained tokens for your stack, we would love to talk.

Ready to secure your tokens?

Learn how Keymate can help you implement DPoP across your stack, from BFF to gateway.

Stay updated with our latest insights and product updates

Frequently Asked Questions