Skip to content

Conversation

@PieterKas
Copy link
Contributor

@PieterKas PieterKas commented Dec 5, 2025

Motivation and Context

This extension defines DPoP (Demonstrating Proof-of-Possession) support for the Model Context Protocol, enabling sender-constrained access tokens that prevent token replay attacks. This extension builds upon the baseline authorization requirements defined in the main Authorization specification.

DPoP binds access tokens to a cryptographic key pair controlled by the client. When accessing MCP server resources, clients must prove possession of the private key by providing a signed proof with each request. This prevents unauthorized use of tokens even if they are intercepted or leaked.

How Has This Been Tested?

TBD

Breaking Changes

No

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • [ x] New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

For inclusion as an auth extension

@D-McAdams

@PieterKas PieterKas changed the title Create 0000-dpop-extension.md 1932-dpop-extension.md Dec 5, 2025
@PieterKas PieterKas changed the title 1932-dpop-extension.md SEP-1932: DPoP Profile for MCP Dec 5, 2025
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@D-McAdams D-McAdams self-assigned this Dec 8, 2025
@D-McAdams D-McAdams added auth SEP draft SEP proposal with a sponsor. extension labels Dec 8, 2025
@D-McAdams D-McAdams requested a review from a team December 8, 2025 16:27
PieterKas and others added 4 commits December 9, 2025 18:51
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
PieterKas and others added 15 commits December 10, 2025 11:10
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Co-authored-by: Brian Campbell <71398439+bc-pi@users.noreply.github.com>
Clarify the security challenge of MCP's architecture and the role of content digest in DPoP proofs.
Clarify the purpose and context of the DPoP extension for the Model Context Protocol, emphasizing the need for additional context in a single-endpoint architecture.
Copy link
Contributor

@localden localden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally supportive of the DPoP direction, with some questions on details (i.e., need for digest implementation).

If an attacker has intercepted both the DPoP proof AND the access token (via network eavesdropping, logs, etc.), they could then (theoretically):

  • Replay the same proof with a different request body
  • Within the short validity window

We then have some practical constraints in place, though:

  • TLS is mandatory - DPoP RFC requires HTTPS. Network eavesdropping shouldn't expose these values. This is different on a compromised machine, but I don't think that's the vector we worry about here?
  • Short validity windows - Proofs are typically valid for seconds/minutes anyway.
  • jti tracking - Servers CAN track jti to prevent any replay (sure, call this "stateful" which is not something you want all the time).
  • Same authorization scope - The attacker can only do what the token authorizes anyway.

So, in a hypothetical scenario, the attack requires:

  • Compromised logs or memory that expose both proof AND token
  • Acting within the validity window
  • The token having authorization for the malicious action (but let's say that the current action is high-value/high-impact).

It feels like we're protecting against a very narrow scenario here, no?


## Abstract

This SEP defines an optional DPoP (Demonstrating Proof of Possession) extension for the Model Context Protocol to support sender-constrained access tokens. The extension binds OAuth 2.0 access tokens to cryptographic key pairs controlled by MCP clients, requiring clients to demonstrate possession of the corresponding private key with each request. To align DPoP with MCP’s single-endpoint architecture, the extension incorporates a content digest into the DPoP proof, allowing the proof to be tied to the specific JSON-RPC request body and ensuring tighter request-level binding.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cryptographic key pairs - are they always controlled by the client or also by the operating environment (e.g., via TPM-bound processes)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@localden the client can use key pairs stored in hardware (like TPMs), or otherwise. The DPoP spec does not specify how key pairs should be managed or stored.

"Control" here means it is able to generate a DPoP proof through access to the keys (even on the OS, that is often only through a handle to the key, not the actual key - depending on the cryptographic sub-system your using).


The DPoP Profile for MCP adapts OAuth 2.0 sender-constrained tokens for use in the Model Context Protocol’s single-endpoint architecture. Standard DPoP binds proofs to the HTTP method and URL, but in MCP all requests use the same method (POST) and endpoint, with the specific operation conveyed in the JSON-RPC body. As a result, traditional DPoP does not distinguish between different MCP operations, since the request semantics are not reflected in the elements covered by the proof. The profile refines the content of the DPoP proof to better account for this characteristic of MCP.

To solve this, the profile adds one key requirement: DPoP proofs must include a cryptographic digest of the JSON-RPC request body (`content_digest`). This binds each proof to a specific request payload, preventing attackers from replaying valid proofs with altered bodies.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I keep referring to the RFC and I am not sure why we need to do things differently for MCP here. The RFC does leave room for extensibility, I am trying to see what we'd be gaining here in terms of attack prevention that is not covered by baseline DPoP.

Copy link
Contributor Author

@PieterKas PieterKas Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@localden - It think this is 100% the right question. The idea was to bind the proof not only to the access token (ath claim) and the HTTP target URI (htu claim) but the actual request message content so that a stolen proof+access token cannot be re-used with different requests that may be sending vastly different messages to the MCP server.

The alternative is to use the nonce capabilities to always force a freshness check - there is some discussion on that further down.


The design emphasizes:

- **Payload binding** — eliminating replay attacks without requiring server-side state.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically, yes, but how frequently is this a risk and in what environments? I generally try to err on the side of simplicity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@localden Token theft has proven to be a popular attack vector. This may be something that is only relevant to enterprises, hence making it part of the auth extensions rather than the core spec.

Do you think it should be part of the core spec as "vanilla" DPoP as well?


## Rationale

The purpose of this extension is to adapt OAuth 2.0 DPoP to the architectural characteristics of the Model Context Protocol (MCP), enabling sender-constrained access tokens to be used effectively in an environment where the standard DPoP mechanism provides limited request differentiation. Although RFC 9449 defines a general-purpose proof-of-possession framework, MCP’s single-endpoint, request-tunneled design means that additional context is needed to distinguish individual operations. This profile introduces a small set of focused enhancements—most notably, mandatory binding to the request payload—to provide that clarity without adding significant complexity or statefulness.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still not entirely sure why this needs to be MCP-specific rather than generic DPoP that prevents token exfiltration?

- A single HTTP method (`POST`),
- With all semantic variation encoded in the JSON-RPC message body.

This means that, for MCP, the `htu` and `htm` claims in a standard DPoP proof they do not meaningfully differentiate purposes or operations. As a result, an attacker who obtains a proof (and valid token) during the proof validity window could replay it with a different JSON-RPC payload, gaining unauthorized access to operations the original client never invoked.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but let's say that's all we use - I still have a token that cannot be exfiltrated off the device (which is the core benefit of DPoP). Replay attacks on the same device seem like a risk, but a significantly lower one, because it implies another level of compromise.

My main worry here is that we'd be adding a bunch of overhead to generate content digests with every request, for little gain.

Copy link
Contributor Author

@PieterKas PieterKas Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but let's say that's all we use - I still have a token that cannot be exfiltrated off the device (which is the core benefit of DPoP). Replay attacks on the same device seem like a risk, but a significantly lower one, because it implies another level of compromise.

@localden - I expect that if you can move the access token off device, you can move the proof as well (both could also be lifted from other sources, like log files or when transiting break and inspect proxies). If we go for a stateless implementation the jti of the proof will not be checked, so you could replay both, with a different message, which I understood to be undesirable.

If we are willing to accept state (jti checking) perhaps we can make the argument that even if you steal both, the proof replay becomes detectable, so we can stick wit vanilla DPoP.

Server supplied nonces would also do the trick as it would force the proofs to be unique per message, so replay of proofs is not viable. I highlighted some options in the "Open questions" section which would rely on server supplied nonces. Repeating here for convenience:

  1. Server supplied nonces: A server-supplied nonce mechanism is generally not recommended because it often requires servers to maintain state (e.g., tracking which nonces have been issued and used). An alternative is a stateless nonce design, where the server provides a nonce constructed from predictable values such as the current time prefixed to a salted hash of the current time and a client_id. This provides a freshness guarantee without requiring nonce storage. The server simply accepts any nonce within a defined time window. This could provide a significant improvement in replay protection because the nonce’s validity is tied to its short lifetime while potentially cryptographically bound to the client (or other information). This approach avoids the need for additional content-hashing requirements and would eliminate the need for MCP-specific modifications to DPoP implementations, at the cost of an extra round-trip at the start of an MCP client/server interaction. We invite discussion on whether this stateless-nonce approach could serve as a viable alternative to introducing content digests.

My main worry here is that we'd be adding a bunch of overhead to generate content digests with every request, for little gain.

Agreed ther eare lots of upsides to "vanilla" DPoP. How do you feel about keeping state (jti) or nonces (maybe without state, but an extra pass to get the initial nonce) to get the per message proof characteristic?

Expanded the abstract section to include details about the DPoP extension's alignment with OAuth 2.0 and its implications for security.
Refine language for clarity and alignment with DPoP standards.
Clarify statelessness requirement by referencing RFC 9449.
@bc-pi
Copy link

bc-pi commented Dec 17, 2025

@localden has better articulated many of the same thoughts I've previously tried to express. Which is largely that I don't think the content digest adds sufficient value to justify the additional (somewhat hidden) complexity and incompatibility with existing implementations, deployments, etc.

I know some folks think they need it. And suggesting that "more security" isn't needed is a tough place to argue from. But maybe it could be made an explicitly defined but wholly optional thing for better coexistence and compatibility?

I am one of the coauthors of RFC9449 btw, which doesn't mean I know what I'm talking about. But does mean I've been involved in many similar discussions in various contexts previously.

@PieterKas
Copy link
Contributor Author

Thanks @bc-pi - Perhaps the first question is if there is enough value in using DPoP as is (without the state based security features like jti and nonce), while accepting the risk of lacking per message binding. The basis of the draft is that it is not sufficient, but would be good to hear the arguments for why it is sufficient (or how else it could be made to be so).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants