Skip to content

MCP OAuth refresh token flow is brokenย #8416

Description

@cstns

Description

Third party MCP agents authenticate with a 30 minute access token and a 30 day refresh token stored on the same AccessToken row. A few things around refreshing them need attention.

Nightly cleanup deletes active grants (witnessed in real use)
The daily expireTokens housekeeper task deletes every token whose expiresAt has passed, without checking refreshTokenExpiresAt. MCP access tokens expire every 30 minutes, so the sweep removes grants that are still refreshable. The agent's next refresh fails with "Invalid refresh_token" and the token vanishes from the UI.
Fix: only delete rows where the refresh token is also expired or missing, the same guard getOrExpire already has.

Missing refresh_token returns a 500
A token request with grant_type=refresh_token and no refresh_token hits sha256(undefined) and throws.
Fix: validate the field and return 400 invalid_request, like the authorization_code branch does.

Refresh cache is per process
Concurrent refresh dedup uses an in-memory cache. With multiple replicas each one mints its own access token and overwrites the same row, so a token handed out by another replica stops working until the next refresh.
Fix: might be worth a shared cache, or dropping the dedup entirely.

Cached refreshes don't extend the window
A refresh served from the cache skips the refreshTokenExpiresAt bump, so the sliding window only extends on some refreshes.
Fix: bump it on every refresh.

MCP tokens look like PATs in the UI
They're stored with ownerType: 'user' and a name, so they show up in the Personal Access Tokens list with the 30 minute access expiry. That already misled a user into thinking his MCP access dies every half hour. Editing one there rewrites expiresAt but never refreshTokenExpiresAt, and clearing the expiry makes the token never expire.
Fix: show the real grant lifetime for MCP tokens or move them out of the PAT list, and keep the PAT edit path away from them.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions