Skip to content

feat: add refresh_token grant support to OAuth proxy#1688

Open
qstearns wants to merge 1 commit intomainfrom
quinn/oauth-proxy-refresh-grant
Open

feat: add refresh_token grant support to OAuth proxy#1688
qstearns wants to merge 1 commit intomainfrom
quinn/oauth-proxy-refresh-grant

Conversation

@qstearns
Copy link
Contributor

@qstearns qstearns commented Feb 25, 2026

Summary

  • Adds refresh_token grant type to the OAuth proxy token endpoint (/oauth/{mcpSlug}/token)
  • MCP clients can exchange a refresh token for a new access/refresh token pair (token rotation)
  • Updates well-known metadata to advertise refresh_token grant support alongside authorization_code

Fixes AGE-1402

Changes

  • token_service.go: Generate refresh tokens in ExchangeAuthorizationCode, add ExchangeRefreshToken method with full token rotation (old token deleted, new pair issued)
  • storage.go: Add RefreshToken field to Token struct, RefreshTokenCacheKey helper, AdditionalCacheKeys returns refresh token key for dual-key lookup
  • types.go: Add RefreshToken to TokenRequest and TokenResponse
  • impl.go: Extract refresh_token from form values, add case "refresh_token" to handleToken
  • wellknown.go: Advertise ["authorization_code", "refresh_token"] in GrantTypesSupported

Test plan

  • Existing tests pass
  • Exchange an auth code → response includes refresh_token
  • POST grant_type=refresh_token&refresh_token=...&client_id=... returns new token pair
  • Old refresh token is invalidated after use (rotation)

🤖 Generated with Claude Code


Open with Devin

MCP clients can now refresh their proxy-issued tokens via
grant_type=refresh_token, receiving a rotated access/refresh pair.
Well-known metadata advertises the new grant type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qstearns qstearns requested a review from a team as a code owner February 25, 2026 03:40
@changeset-bot
Copy link

changeset-bot bot commented Feb 25, 2026

⚠️ No Changeset found

Latest commit: c3145ce

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gram-docs-redirect Ready Ready Preview, Comment Feb 25, 2026 3:40am

Request Review

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

@linear
Copy link

linear bot commented Feb 25, 2026

@qstearns qstearns added the preview Spawn a preview environment label Feb 25, 2026
@speakeasybot
Copy link
Collaborator

speakeasybot commented Feb 25, 2026

🚀 Preview Environment (PR #1688)

Preview URL: https://pr-1688.dev.getgram.ai

Component Status Details Updated (UTC)
✅ Database Ready Existing database reused 2026-03-03 17:16:10.
✅ Images Available Container images ready 2026-03-03 17:15:53.

Gram Preview Bot

qstearns added a commit that referenced this pull request Mar 5, 2026
## Summary
- When an MCP client presents a valid proxy token whose upstream
credentials have expired, the server now automatically attempts to
refresh them via the upstream provider's token endpoint
- Captures and encrypts upstream refresh tokens during the initial OAuth
token exchange
- Stacked on #1688

Fixes
[AGE-1402](https://linear.app/speakeasy/issue/AGE-1402/bug-external-oauth-mcp-servers-log-users-out-no-token-refresh)

## Changes
- **providers/provider.go**: Add `RefreshToken` to
`TokenExchangeResult`, add `RefreshToken` method to `Provider` interface
- **providers/custom.go**: Capture `refresh_token` from upstream
response, implement `RefreshToken` method (posts
`grant_type=refresh_token` to upstream)
- **providers/gram.go**: Stub `RefreshToken` (not supported for Gram
provider)
- **storage.go**: Add `RefreshToken` field to `ExternalSecret`
- **grant_manager.go**: Accept, store, and encrypt upstream refresh
tokens in grants
- **token_service.go**: Add `ErrExpiredExternalSecrets`,
`ErrNoUpstreamRefreshToken` sentinels; `ValidateAccessToken` returns
token + error on expired external secrets (instead of deleting); add
`RefreshExternalSecrets` method; encrypt/decrypt
`ExternalSecret.RefreshToken`
- **impl.go** (oauth): Pass upstream refresh token through callback
flow, add `RefreshProxyToken` method
- **mcp/oauth_service.go**: Add `RefreshProxyToken` to `OAuthService`
interface
- **mcp/impl.go**: Catch `ErrExpiredExternalSecrets` in custom provider
auth path, attempt upstream refresh before failing

## Test plan
- [ ] Existing tests pass
- [ ] Custom OAuth provider returns refresh_token → stored and encrypted
in ExternalSecrets
- [ ] When upstream access token expires, proxy transparently refreshes
it on next MCP request
- [ ] When upstream refresh also fails, client gets 401 as expected

🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- devin-review-badge-begin -->

---

<a href="https://app.devin.ai/review/speakeasy-api/gram/pull/1689"
target="_blank">
  <picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open with Devin">
  </picture>
</a>
<!-- devin-review-badge-end -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Spawn a preview environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants