Google OAuth DPoP for refresh tokens: What it protects and how to adopt
Google now binds OAuth refresh tokens to a client key with DPoP; access tokens stay Bearer. What that protects, what to build, and who can use it.
DPoP (Demonstrating Proof of Possession, RFC 9449) binds an OAuth token to a key pair the client generates, so the token only works alongside a fresh proof signed with that private key. Google's OAuth platform now applies DPoP to refresh tokens only: a stolen Google refresh token is useless without the client's private key, while Google access tokens remain ordinary bearer tokens that work for anyone holding them until they expire.
On October 15, Google announced DPoP support for its OAuth platform. The binding covers refresh tokens, and access tokens keep behaving exactly as they do today. The announcement describes support, not a requirement, and sets no deadline. DPoP just stopped being a spec you meant to read someday: if your app holds Google refresh tokens, there is now a decision with your name on it.
The decision is narrower than the headline suggests, and the implementation work is larger. Here is what Google actually bound, what your client has to build to use it, and which architectures can adopt it at all. (For the protocol itself, our walkthrough of RFC 9449 covers the proof JWT, the key thumbprint binding and nonces in detail. This post sticks to what is specific to Google.)
What did Google bind, and what did it leave alone?
Google implemented DPoP for one credential and said why:
The argument underneath that is a cost calculation:
Google doesn't publish a latency figure or a cost model for the per-request verification it declined to take on, so treat the trade as a judgment call made at Google's scale rather than a number you can reuse for your own APIs.
Google frames plain bearer tokens as a "finders-keepers" model exposed to infostealer malware like RedLine and NodeStealer, and to adversary-in-the-middle attacks. (Our piece on token replay attacks covers why MFA doesn't help once a token is out.) Under the new scheme, a refresh token exfiltrated by one of those is useless to an attacker without the matching private key. An access token pulled out of the same compromised process is a working credential until it expires, and Google says its access tokens are "typically valid for 1 hour or less." The example token responses in Google's DPoP adoption guide still come back with "token_type": "Bearer" and "expires_in": 3599.
Google also goes further than the RFC's default here. RFC 9449 says refresh tokens issued to confidential clients aren't bound to the DPoP key, because client authentication already sender-constrains them: a stolen refresh token is useless without the client's secret. Google binds them for confidential clients too, which adds a second layer on top of the secret rather than closing an open hole.
So adopting it buys you one specific thing: the long-lived credential stops being a permanent backdoor. Persistent offline impersonation becomes a one-hour problem instead of an indefinite one. Everything you already do about access token theft (short lifetimes, narrow scopes, revocation) still has to do its job.

Which architectures can adopt Google's DPoP?
The value of sender-constraining scales with how exposed the credential already is. A Google refresh token that a browser can reach is worth binding. One in a server-side secret store, refreshed with a client secret, gains less: the RFC already treats it as sender-constrained, and DPoP adds defense in depth for the case where the token and the secret leak together.
Google says it recommends DPoP for refresh tokens across all three architectures in the IETF's OAuth 2.0 for Browser-Based Applications draft: Backend-for-Frontend, Token-Mediating Backend and Browser-based OAuth Client. That draft is draft-ietf-oauth-browser-based-apps-27, by Aaron Parecki, Philippe De Ryck and David Waite, intended as a Best Current Practice and now in the RFC Editor queue.
That browser row has a wrinkle the announcement doesn't mention. Google's DPoP adoption guide is blunt about the SPA case:
Read the two documents together and the recommendation is architectural. Google's code exchange example sends client_secret alongside the authorization code, which a public browser client doesn't have. If you call Google APIs from a browser-only client today, adopting DPoP with Google means adopting a confidential backend first, which the IETF draft already strongly recommends for business applications, sensitive applications and anything handling personal data.
What does your client have to implement?
Google's announcement is explicit that this is more than a header: "DPoP requires client-side cryptographic key generation, strict timestamp and nonce handling," plus computing the htm, htu, iat and jti claims, exact header construction and proof JWT construction. Four pieces of work, in order.
1. Generate and hold a key pair
Google's guide says to generate an EC P-256 (ES256) key pair and put the public key in the jwk parameter of the JOSE header; RSA works but isn't recommended "due to higher computational costs." Since Google steers browser apps to a backend, the key normally lives on your server. If you do generate one in a browser, the IETF draft's pattern is a non-extractable WebCrypto key pair stored in IndexedDB, so a script can sign with it but can't copy it out:
Non-extractable is not the same as safe from XSS. RFC 9449 is direct about it: a script running in the page "has access to utilize the signing key; thus, it can produce DPoP proofs," and the spec treats that as out of scope. The key stops offline replay, not an attacker who is in your origin while the user is online.
The JOSE header ships with every proof:
2. Build the code-exchange proof
For requests to Google's token endpoint, htm and htu are fixed values, while iat and jti change per request. On the authorization code exchange, jti is derived rather than random: Google specifies the Base64URL-encoded SHA-256 hash of the authorization code.
The response returns a DPoP-bound refresh token along with a DPoP-Nonce header.
3. Build the refresh proof, with the nonce
On refresh, jti becomes a unique random string per request (the guide uses crypto.getRandomValues(new Uint8Array(24)), Base64URL-encoded) and the payload gains a nonce claim carrying the last DPoP-Nonce value the server sent.
4. Handle the 400 as a normal step
Each nonce is single-use. An expired, incorrect or reused nonce is rejected with an HTTP 400 that carries a fresh nonce for the retry:
The subtle part is what Google calls workflow isolation. When a client moves from the initial authorization code exchange to a token refresh request, "the server unconditionally rejects the nonce with an HTTP 400 use_dpop_nonce challenge to establish a fresh nonce namespace for the new workflow." A client that treats the first refresh after sign-in as a hard failure will log an error on every session. Build the sign-and-retry loop first, then the happy path.

Does DPoP work across multi-agent chains?
DPoP works at the HTTP header level rather than the transport layer, which is why Google calls it a fit for environments "including emerging Model Context Protocol (MCP) and autonomous agent architectures," while transport-layer binding like mTLS "often breaks or terminates at modern cloud proxies and load balancers." (Our comparison of bearer and sender-constrained tokens covers the mTLS trade-off.)
Resilient per hop and composable across hops are different properties, and Google says so: "In a multi-agent chain, Agent B cannot sign a DPoP proof on behalf of Agent A without access to Agent A's private key." Sharing the key, Google adds, would violate the core security principles of DPoP.
The usual fix is RFC 8693 token exchange, where the middle agent trades the upstream token for one of its own (our post on the multi-hop delegation problem walks through it). Google's consumer OAuth endpoint "does not support OAuth 2.0 Token Exchange (RFC 8693)," and the recommendation for multi-agent or federated workloads is Google Cloud Secure Token Service instead, where the intermediate agent can "exchange the upstream token for a new, short-lived federated token cryptographically bound to its own local key pair."
Look at what that means for your topology. The consumer OAuth surface and the Cloud surface are different systems with different trust models, and an agent chain that touches Gmail or Drive on a user's behalf has to cross between them. A design that assumed one DPoP key could carry an entire tool-calling chain needs a key and a token per hop, plus a second authorization server in the path. Google documents the refresh-proof mechanics claim by claim and leaves the STS exchange as a pointer, so budget research time for the hop you care most about. Our look at DPoP in the MCP SDK covers the client-side half.
What doesn't Google's DPoP cover?
DPoP doesn't protect web application session cookies, which Google names as a primary infostealer target. That gap belongs to Device Bound Session Credentials, which bind cookies to device hardware such as a TPM; DBSC has been generally available in Chrome on Windows since May 2026. (Short session lifetimes remain the control you own on your side.) Separately, Google says Workspace Context-Aware Access uses DPoP to validate agent identities at the Agent Gateway for Gemini Enterprise Agent Platform workloads, a feature Google Cloud's docs list as Preview.
Google's announcement and adoption guide say nothing about native or mobile applications. If you ship a desktop or mobile client, the keystore questions are real, but the answers aren't in this release.
Should you adopt it now?
If your Google refresh tokens live anywhere a script or a stolen laptop can reach them, move them behind a backend and bind them. The work is a key pair, four claims and a retry loop, and Google publishes an adoption guide plus a SKILL.md for agent-assisted implementation. If they live in a server-side secret store, schedule it rather than sprint it. While you're in that code, it's worth knowing how Google's refresh tokens already behave (they don't rotate, and they lapse after six months unused), which our survey of refresh token behavior across fourteen providers covers.
Then go look at the other half. A token-mediating backend that binds its refresh token and still hands a plain access token to the browser has closed the permanent hole and left the hourly one open: the same XSS payload that can't exfiltrate the key still walks off with a credential good until expiry. Google has split token theft into two risks, a long-lived credential you can now make worthless and a short-lived one you still can't. Your incident runbook should say which half you fixed.