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 youregress allowlist. defineIntegration() rejects the definition otherwise, telling you which origin to add.
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:
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
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_postonly (noprivate_key_jwt) - No per-account secret path for OAuth-form integrations
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 byclientIdSecretName / 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
- Activation form — the
OAuthActivationFormvariant - Capability context — use ctx in onTokens