Skip to content

feat(sep-1932): DPoP refresh token binding - #524

Open
nbarbettini wants to merge 2 commits into
modelcontextprotocol:mainfrom
nbarbettini:feat/1932-refresh-binding
Open

nbarbettini wants to merge 2 commits into
modelcontextprotocol:mainfrom
nbarbettini:feat/1932-refresh-binding

Conversation

@nbarbettini

Copy link
Copy Markdown
Contributor

Discussed here: https://discord.com/channels/1358869848138059966/1552316422951149639

Motivation and Context

Refresh tokens are long-lived credentials that may remain on a client device for months. If one is stolen, it can be exchanged repeatedly for new access tokens. RFC 9449 protects public DPoP clients by binding issued refresh tokens to the same key used at the token endpoint.

This change adds a client check for that requirement. The test authorization server can now issue and rotate refresh tokens on an opt-in basis. When a client uses one, it must present a valid DPoP proof for the original key.

Using refresh tokens remains optional. The check is SKIPPED when the client does not use the issued refresh token, and fails only when a refresh is attempted without a valid proof or with a different key.

Refresh is exercised within the existing auth/dpop scenario. This keeps the full OAuth flow in one scenario and avoids duplicated setup or wall-clock expiry waits. The DPoP fixture explicitly registers a public client, and failed refresh observations remain sticky so a later successful retry cannot hide an earlier violation.

How Has This Been Tested?

  • Full test suite: 636 tests passed
  • Typecheck, ESLint, and Prettier passed
  • Positive test for refresh with the bound DPoP key
  • Negative test for refresh without a DPoP proof
  • Negative test for refresh with a different key
  • Test confirming that clients may ignore the optional refresh token
  • Test confirming that a later successful retry does not erase an earlier invalid refresh
  • Test confirming that unrelated authorization scenarios do not receive refresh tokens

Breaking Changes

No breaking changes... if you are already doing DPoP correctly 😉

Clients participating in the DPoP extension may receive a new FAILURE if they use a refresh token without proving possession of the key to which it is bound. Clients that do not use refresh tokens are unaffected.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • 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

nbarbettini and others added 2 commits September 24, 2026 14:51
RFC 9449 §5 binds a public client's refresh token to the DPoP key from the code exchange. The test authorization server now issues and rotates those tokens, and auth/dpop-refresh checks the client proves the same key.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@pkg-pr-new

pkg-pr-new Bot commented Sep 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/@modelcontextprotocol/conformance@524

commit: 87875e9

@nbarbettini nbarbettini changed the title feat(sep-1932): DPoP refresh binding feat(sep-1932): DPoP refresh token binding Sep 24, 2026
@nbarbettini
nbarbettini marked this pull request as ready for review September 25, 2026 13:25

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant