## Summary
Replaces the request-host-header-derived OAuth `redirect_uri` with a
config-driven `customCallbackUrl` field on each environment-level OAuth
provider.
Resolution of the `redirect_uri` we send to providers (and that
customers register in their provider app config):
- **Shared providers** → always the stack-auth-branded callback, so
Stack's shared OAuth apps keep working. `customCallbackUrl` is
schema-forbidden when `isShared` is true.
- **Custom + `customCallbackUrl` set** → the configured URL verbatim.
- **Custom without it (legacy)** → the stack-auth-branded callback, so
providers registered before this field are unaffected.
- **New custom providers set up in the dashboard** → the env-aware
hexclave-branded callback (prod → `api.hexclave.com`, dev/staging →
siblings, self-host/localhost → `NEXT_PUBLIC_STACK_API_URL` unchanged).
## Details
- **Schema** (`schema.ts`, `schema-fields.ts`): optional
`customCallbackUrl` after `clientSecret`, with a `.when('isShared')`
rule rejecting any value for shared providers; added to the provider
default factory.
- **Shared host helper** (`utils/cloud-hosts.tsx`, new):
`CLOUD_HOST_PAIRS` moved into stack-shared with `getCloudApiUrlSiblings`
/ `getStackAuthApiBaseUrl` / `getHexclaveApiBaseUrl`;
`request-api-url.ts` re-exports it so the JWT `iss` logic is untouched.
- **Runtime** (`oauth/index.tsx` + all 13 provider `create()`s):
`getProvider` resolves the full `redirect_uri` from config instead of
the request host; providers now take `redirectUri` instead of `apiUrl`.
The JWT `iss` path still uses the request host.
- **Dashboard** (`page-client.tsx`, `providers.tsx`,
`oauth-callback-url.ts` new): brand-new custom providers get the
hexclave callback; existing providers keep whatever they had (edits
never silently move a registered redirect URL); the displayed Redirect
URL mirrors backend resolution.
- **Docs** (`migration.mdx`): existing `api.stack-auth.com` callbacks
keep working; only recreated providers use the hexclave URL.
## Notes / scope decisions
- **Dashboard-only injection**: SDK/CLI/legacy-config-created custom
providers fall back to the stack-auth callback (they don't auto-get the
hexclave URL).
- **shared → standard** conversions keep the stack-auth fallback rather
than flipping to hexclave (the safe path that never breaks a registered
redirect).
## Test plan
- [x] `typecheck` + `lint` green across stack-shared, backend,
dashboard, e2e
- [x] cloud-hosts unit tests, schema tests, schema fuzzer pass
- [x] e2e: shared-provider `customCallbackUrl` rejected (400);
standard-provider `customCallbackUrl` accepted and round-trips
- [ ] e2e OAuth authorize/callback flow (needs running stack) — reasoned
unaffected since localhost isn't a cloud host, so the redirect base
stays localhost as before
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a per-provider `customCallbackUrl` for OAuth `redirect_uri`,
removing the request-host dependency and making redirects predictable.
Shared providers always use the Stack-branded callback; new or converted
custom providers default to the Hexclave-branded callback. Existing
callbacks keep working; no changes needed unless you recreate or convert
a provider.
- **New Features**
- Added `customCallbackUrl` on provider configs (URL-validated;
forbidden when `isShared` is true).
- `getProvider` now resolves a config-driven `redirectUri`; providers
take `redirectUri` instead of `apiUrl` (pure resolver with in-source +
e2e tests to lock legacy behavior).
- Introduced `@stackframe/stack-shared` `utils/cloud-hosts.tsx` and
dashboard helpers to show the resolved Redirect URL and set the Hexclave
callback for new providers and when converting shared → standard.
- **Bug Fixes**
- OAuth callback now handles legitimate cross-host flows by recording
the authorize host and skipping the host-scoped CSRF cookie when
authorize and callback hosts differ, relying on server-side state and
PKCE.
<sup>Written for commit
|
||
|---|---|---|
| .. | ||
| api | ||
| guides | ||
| images | ||
| openapi | ||
| sdk | ||
| snippets | ||
| .gitignore | ||
| apps-sidebar-filter.js | ||
| code-language-labels.js | ||
| docs.json | ||
| index.mdx | ||
| migration.mdx | ||
| package.json | ||
| README.md | ||
| style.css | ||
docs-mintlify
How to run the Mintlify docs preview locally from this repository.
Prerequisites
-
Node.js
>=20.17.0 -
pnpm -
Repository dependencies installed (
pnpm installfrom repo root) -
OpenAPI specs in
openapi/are committed to git. Hosted Mintlify cannot run monorepo codegen on deploy, so these files must be present in the repo for production docs.When you change API route OpenAPI metadata, regenerate and commit the four specs from the repo root:
pnpm run --filter @stackframe/backend codegen-docs git add docs-mintlify/openapi/That writes
client.json,server.json,admin.json, andwebhooks.jsonintodocs-mintlify/openapi/(and intodocs/openapi/for the legacy Fumadocs app). CI fails ifpnpm codegenproduces different output than what is committed (see root lint-and-build workflow).
Run locally
From the repository root:
pnpm -C docs-mintlify run dev
This starts Mintlify in docs-mintlify on http://localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}04 (for example, http://localhost:8104 with the default prefix).
From inside docs-mintlify, you can also run:
pnpm dev
Useful variants:
# Override the default port
pnpm -C docs-mintlify run dev -- --port 3333
# Skip OpenAPI processing for faster iteration
pnpm -C docs-mintlify run dev -- --disable-openapi
Search + assistant in local preview
If you want local search and the Mintlify assistant:
pnpm -C docs-mintlify run login
pnpm -C docs-mintlify run status
Then re-run pnpm -C docs-mintlify run dev.
Package scripts
From repo root:
pnpm -C docs-mintlify run lint
pnpm -C docs-mintlify run typecheck
pnpm -C docs-mintlify run build
pnpm -C docs-mintlify run clean
lint runs both mint validate and mint broken-links.