Skip to content

feat(teams): persist + refresh IdP subject token at login (spec 074, MCP-1036)#601

Open
Dumbris wants to merge 1 commit into
mainfrom
074-t3-idp-subject-token
Open

feat(teams): persist + refresh IdP subject token at login (spec 074, MCP-1036)#601
Dumbris wants to merge 1 commit into
mainfrom
074-t3-idp-subject-token

Conversation

@Dumbris
Copy link
Copy Markdown
Member

@Dumbris Dumbris commented Jun 4, 2026

Summary

Spec 074 T3 (MCP-1036). Builds on T1 (#587 credential store) and T2 (#588 config), both merged.

Today HandleCallback fetches userinfo then discards the provider token. This PR captures it when teams.store_idp_tokens is enabled and adds a get-or-refresh seam that never serves a stale token.

What changed

  • Capture at login (oauth_handler.go): after code exchange + user upsert, when store_idp_tokens is on, persist the IdP access + refresh token encrypted (AES-256-GCM) via the credential store as an idp_subject_token record keyed by userID (empty serverKey). Best-effort — a write failure never breaks login.
  • Refresh seam (idp_subject_token.go): GetValidIDPSubjectToken(ctx, userID) returns a non-expired token, refreshing via the provider refresh_token grant when expired/near-expiry (60s skew). When no valid token can be produced — absent, store disabled, expired-and-not-refreshable, or refresh failed — it returns ErrReauthRequired, never a stale token (FR-005). This is the prerequisite consumed by the credential resolver (Path A, MCP-1039).
  • Provider (oauth_providers.go): OAuthProvider.RefreshAccessToken (refresh_token grant, RFC 6749 §6).
  • Wiring (teams/setup.go): construct the broker store from MCPPROXY_CRED_KEY / teams.credential_encryption_key and attach via SetCredentialStore.

Default-off (FR-006)

With store_idp_tokens false (default) or no encryption key configured, login behaves exactly as before — no storage, store constructed disabled, capture silently skipped.

Tests (TDD, -tags server)

  • capture-at-login stores encrypted record (enabled)
  • no storage when disabled
  • refresh near/at expiry re-mints + re-persists (incl. rotated refresh token)
  • expired + not refreshable → ErrReauthRequired
  • absent token → ErrReauthRequired
  • store disabled → ErrReauthRequired
  • valid token returned unchanged

Verification

  • go build -tags server ./cmd/mcpproxy ✅ and go build ./cmd/mcpproxy (personal unaffected) ✅
  • go test -tags server ./internal/teams/... -race ✅ (all packages)
  • gofmt/go vet clean; CI lint config clean on changed files

Docs

No docs change, matching T1/T2 precedent (neither shipped docs for these keys). The feature is server-edition, default-off, and not yet user-consumable until the resolver + header injection land (T6+). Consolidated spec-074 user docs belong with that completion.

Related #588, #587. Blocks MCP-1039 (T6 resolver).

Gate 3: opening for review/CI only — I do not merge.

…MCP-1036)

Today HandleCallback fetched userinfo then discarded the provider token. When
teams.store_idp_tokens is enabled, capture the IdP access + refresh token at
login and persist it encrypted (AES-256-GCM) via the credential store as an
idp_subject_token record keyed by userID (empty serverKey).

Adds GetValidIDPSubjectToken: returns a non-expired token, refreshing via the
provider refresh_token grant when expired/near-expiry; when no valid token can
be produced (absent, store disabled, expired-and-not-refreshable, or refresh
failed) it returns ErrReauthRequired — never a stale token. This is the
prerequisite seam consumed by the credential resolver (Path A token exchange,
MCP-1039).

Default-off: with store_idp_tokens false (default) or no encryption key, login
behaves exactly as before — no storage. Persist is best-effort: a write failure
never breaks login.

- OAuthProvider.RefreshAccessToken: refresh_token grant (RFC 6749 §6)
- OAuthHandler.credStore + SetCredentialStore; wired in teams/setup.go
- TDD: capture-when-enabled, no-storage-when-disabled, refresh-near-expiry,
  expired-not-refreshable -> re-auth, absent -> re-auth, store-disabled -> re-auth

FR-004, FR-005, FR-006. Related #588 (T2 config), #587 (T1 store).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6344481
Status: ✅  Deploy successful!
Preview URL: https://1b70ce16.mcpproxy-docs.pages.dev
Branch Preview URL: https://074-t3-idp-subject-token.mcpproxy-docs.pages.dev

View logs

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

📦 Build Artifacts

Workflow Run: View Run
Branch: 074-t3-idp-subject-token

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (24 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 26968052572 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

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.

2 participants