Connector auth
Grounded in docs/architecture/connector-tenant-contract.md. The contract’s core idea: two hosts, two jobs, and secrets that never touch docs, chat, or code.
Two hosts, two jobs
Section titled “Two hosts, two jobs”| Surface | Host | Example |
|---|---|---|
| App UI / login | Tenant host | acme.preshos.com |
OAuth redirect_uri and webhooks |
Canonical preshos.com via appBaseUrlFromEnv() (lib/platform/app-base-url.ts) |
https://preshos.com/... |
Never use the tenant hostname for OAuth redirect_uri or webhooks. The canonical host is derived server-side with appBaseUrlFromEnv(). If you are constructing a callback URL from a tenant hostname, that is wrong.
Hostname baseline (WS-0): wildcard *.preshos.com, WorkOS redirect https://*.preshos.com/callback, NEXT_PUBLIC_APP_URL=https://preshos.com.

OAuth state: HMAC-carried context
Section titled “OAuth state: HMAC-carried context”OAuth state carries the org/connection context as an HMAC signed with AUTH_SECRET:
- State carries
orgId/connection, signed, not trusted on sight. Verify the HMAC before acting on anything in it. - Slack: cookie
Domainsuffix contract lives inlib/integrations/slack/package-oauth-cookie.ts. Follow that file for the exact domain behavior. - HubSpot: reuses the shared helpers and signs the redirect host into state. The callback verifies it is answering for the host it expects.
Callback allowlist in proxy.ts
Section titled “Callback allowlist in proxy.ts”Only exact callback paths are allowed, currently:
/api/v1/integrations/packages/slack/oauth/callback/api/v1/integrations/packages/hubspot/oauth/callback
A new package’s callback path must be added to the allowlist in proxy.ts or the callback is rejected. Exact match: no prefixes, no wildcards, no “close enough.”
Secrets: vaulted, granted, never pasted
Section titled “Secrets: vaulted, granted, never pasted”- Tenant Vault (
ai.secret_grant): per-tenant secret grants. The credential holder provisions through the product flow; code references the grant, never the value. - GCP Secret Manager: platform-side secrets (for example, signing keys like
AUTH_SECRET). - Rotation/expiry handling per connector: not in pasted sources. Confirm in the product repo before documenting.
Checklists
Section titled “Checklists”Wiring auth for a connector package
Section titled “Wiring auth for a connector package”redirect_uriand webhooks use canonical host viaappBaseUrlFromEnv(). Grep for tenant-hostname construction and remove it.- OAuth state HMAC-verified with
AUTH_SECRETbefore use; carries org/connection - Package callback path added to the
proxy.tsallowlist (exact path) - Slack: cookie domain follows
package-oauth-cookie.ts; HubSpot: redirect host signed into state - Secrets referenced as grants (Tenant Vault) or SM references. No values in code, tests, fixtures, or docs.
Enabling auth for a tenant
Section titled “Enabling auth for a tenant”- Credential provided by the holder through the product flow (never docs or chat)
- Verified in a scoped test session before real work, in this order: read, draft, gated execute
- Environment tool list shows exactly the intended tools
- Binding coordinates recorded: org, package, connection

