Skip to content

feat(connect): add exchangeToken for RFC 8693 token exchange - #1741

Open
m0tzy wants to merge 2 commits into
mainfrom
devin/1793080915-connect-token-exchange
Open

m0tzy wants to merge 2 commits into
mainfrom
devin/1793080915-connect-token-exchange

Conversation

@m0tzy

@m0tzy m0tzy commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds workos.connect.exchangeToken(options) for the Connect token exchange grant (workos/workos#77337): a first-party Connect Application exchanges an access token audienced to one Resource Indicator for one audienced to another.

const { accessToken, expiresIn, scope } = await workos.connect.exchangeToken({
  issuer: 'https://auth.example.com',   // the environment's issuer / AuthKit domain
  clientId: 'client_...',               // first-party Connect Application
  clientSecret: '...',
  subjectToken: incomingAccessToken,    // aud = https://mcp.example.com
  resource: 'https://api.example.com',  // registered Resource Indicator
  scope: 'email openid',                // optional narrowing
});

Unlike the rest of the SDK this does not call api.workos.com with the API key: the grant lives at ${issuer}/oauth2/token and is authenticated with the Connect Application's client ID + secret, so the method builds a FetchHttpClient for the issuer (no Authorization header), POSTs the form-encoded RFC 8693 parameters (grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token, subject_token_type/requested_token_type=…:access_token, resource, optional scope), and reuses WorkOS.handleHttpError/readResponseJSON so OAuth errors (invalid_grant, invalid_target, invalid_scope, unauthorized_client, unsupported_grant_type) surface as OauthException like everywhere else; a 401 with an RFC 6749 body (invalid_client) is also mapped to OauthException rather than the API-key UnauthorizedException. The issuer client is built from the SDK's fetchFn/timeout/maxRetries/config options so proxies and deadlines behave like every other call. The hand-written code is fenced with @oagen-ignore so regeneration keeps it. Those two helpers go from private to /** @internal */ for that.

Response is deserialized to { accessToken, issuedTokenType, tokenType, expiresIn, scope? } (scope omitted when absent).

Tests: connect.spec.ts covers URL/body/no-Authorization-header, scope omission, 400 and 401 OauthException mapping, and custom fetchFn propagation. npm run lint, npm run format, tsc --noEmit clean.

Documentation

Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.

[x] Yes

Reference page for the grant: workos/workos#77556 (Node snippet to follow once this method's name is settled).

Link to Devin session: https://app.devin.ai/sessions/82cf47fee15f412ab6c3bb2a3e12b2dd
Open in Devin Desktop: https://app.devin.ai/desktop/session/82cf47fee15f412ab6c3bb2a3e12b2dd?variant=devin
Requested by: @m0tzy

…the environment issuer

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@m0tzy
m0tzy requested review from a team as code owners October 27, 2026 06:04
@m0tzy
m0tzy requested a review from cmatheson October 27, 2026 06:04
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

Original prompt from madison.packer

Can we open a PR that does token exchange for Connect where we accept a token with one resource and issue it for a new resource (among an MCP customer's configured resource indicators)

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin resolved all 4 findings on 8d678e2

Fixed by Devin (4)

  • Token exchange bypasses configured fetch transport
  • Token exchanges ignore timeout and retry settings
  • OAuth 401 responses lose error details
  • Generated Connect method needs preservation

View all findings in Devin Review

Devin Review

@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge; no new blocking issues were found.

Summary

Adds connect.exchangeToken to exchange an access token at the supplied issuer’s /oauth2/token endpoint.

  • Connect exchanges an access token for one aimed at another resource.

The previously reported connection-settings issue is fixed. No new actionable issues were found in the changes since the previous review.

Diagram

sequenceDiagram
  participant App
  participant Connect
  participant Issuer
  App->>Connect: exchangeToken(options)
  Connect->>Issuer: POST /oauth2/token with application credentials
  alt Successful exchange
    Issuer-->>Connect: Token response
    Connect-->>App: Camel-cased token fields
  else OAuth error
    Issuer-->>Connect: OAuth error body
    Connect-->>App: OauthException
  end
Loading

Reviews (2) · Last reviewed commit: "fix(connect): carry fetchFn/timeout/maxR..." · Reviewed by Greptile

Comment thread src/connect/connect.ts Outdated
…p 401 OAuth errors, mark hand-written code for oagen

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@greptileai review

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant