Skip to main content
OAuth lets users connect via a third-party authorization server. You declare the provider endpoints and scopes and name the client-credential secrets; the transport owns the flow — authorize redirect, state, callback, and code exchange. You persist the tokens in onTokens().

Basic OAuth

Declaration

Token endpoint client authentication

provider.tokenAuthMethod says how the token endpoint expects the client to authenticate. It defaults to 'client_secret_basic': an HTTP Basic Authorization header. Set 'client_secret_post' for a provider that wants client_id and client_secret in the form body instead and rejects Basic. LinkedIn is one. Check the provider’s docs for which it accepts; a mismatch shows up as exchange_failed, and the provider’s (redacted) response is in the host and harness logs.

Endpoints

Both must be HTTPS and covered by your egress allowlist. defineIntegration() rejects the definition otherwise, telling you which origin to add.
You do not declare a redirectUri — the schema is .strict() and rejects it. The host owns the callback route and builds the URI from its own origin. The host serves three routes for every partner integration that declares oauth:
Register the callback with your provider:
The code exchange runs through your integration’s allowlisted ctx.fetch, authenticating to the token endpoint with HTTP Basic client auth unless provider.tokenAuthMethod says otherwise, and then calls onTokens(ctx, tokens) with the real capability context.

Client helpers

Reference the routes through @ekanos/sdk/hooks rather than spelling them yourself: pass partnerOAuthConnectPath(slug) as OAuthActivationForm’s connectPath, createPartnerConnectionCheck(slug) as its checkConnection, and partnerOAuthErrorParam(slug) as its errorParam. See Activation form. On failure the callback appends <slug>_oauth_error=<code> to the return URL. partnerOAuthErrorParam maps the codes to messages: wrong_workspace, app_not_set_up and organization_access were added in 0.4.0. In short: wrong_workspace means the person consented in a different account or workspace than the integration is configured for; app_not_set_up means the provider refused the configured OAuth app (its client or scopes); organization_access means organization-wide access was granted where the integration only takes a person’s own access. The apostrophes in those messages are typographic (’), so match ticket text accordingly.

Tokens object

All fields except accessToken are optional. onTokens runs after the code exchange. The contract also covers refreshes, but the host does not drive token refresh yet — see Limitations.

Common patterns

Store tokens separately

Log for audit

Limitations

Known gaps in the hosted flow:
  • No host-driven token refresh
  • No partner disconnect hook
  • Token-endpoint client authentication is HTTP Basic or client_secret_post only (no private_key_jwt)
  • No per-account secret path for OAuth-form integrations
The harness runs a local OAuth loop in live mode — see Live mode. onTokens is also an ordinary async function, so you can unit-test it directly against createMockContext():

Troubleshooting

Endpoint origin not covered by egress

defineIntegration() reports, for example, oauth.provider.tokenUrl: its origin https://connect.acme.example is not covered by this definition's egress list [...] and names the origin to add. Both provider.authorizationUrl and provider.tokenUrl must have their origins in your egress list:

Connect fails with not_configured

The host could not resolve the client credentials named by clientIdSecretName / clientSecretSecretName. Those names are strings you choose; the values are credentials an administrator adds for your workspace, not fields a user enters. Ask an administrator to add them under the exact names your declaration uses.

Next steps