mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
## Summary
When the backend serves both `api.stack-auth.com` and `api.hexclave.com`
from the same deployment, signed JWT `iss` claims and OAuth
`redirect_uri` values need to match the host the customer's SDK actually
talks to — otherwise customers with hardcoded issuer checks or
registered OAuth callback URLs break when their SDK upgrades. This PR
makes both follow the request host.
Closes the "Interpretation B" plan from our earlier discussion.
## Changes
### Backend — request-host-derived `iss` and `redirect_uri`
- **New helper**
[`apps/backend/src/lib/request-api-url.ts`](apps/backend/src/lib/request-api-url.ts)
exports `getApiUrlForRequest(req)` and `getApiUrlForHost(host)`. A
consolidated `CLOUD_HOST_PAIRS` constant is the single source of truth
for the stack-auth ↔ hexclave host pairs (prod, dev, staging). Both the
allowlist here and the validator alias map in `tokens.tsx` derive from
it, so they can never drift again.
- **JWT issuer per request**
([`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)) —
`getIssuer` now takes `apiUrl`.
`generateAccessTokenFromRefreshTokenIfValid` and `createAuthTokens`
accept an `apiUrl` parameter that flows into the `iss` claim.
`getAllowedIssuers` stays env-driven with the bidirectional alias map,
so tokens cross-validate across hosts.
- **OAuth `redirect_uri` per request** — all 12 providers +
`MockProvider` now take `apiUrl` and use it to build `redirect_uri =
apiUrl + "/api/v1/auth/oauth/callback/<provider>"`. `getProvider()`
accepts an `{ apiUrl }` option and forwards it.
- **OAuth2Server factory** — the module-level `oauthServer` singleton
became a per-request `createOAuthServer({ apiUrl })` factory so
`OAuthModel.generateAccessToken` mints tokens with the right `iss`. Used
in the callback route, the token route, and the cross-domain-authorize
helper.
- **Token-minting call sites updated** — all 10 `createAuthTokens`
invocations (password sign-up/sign-in, sessions create,
sessions/current/refresh, anonymous sign-up, apple-native callback,
MFA/OTP/passkey sign-in, OAuth model token exchange), plus the
CLI-complete `generateAccessTokenFromRefreshTokenIfValid` direct call,
now pass `getApiUrlForRequest(fullReq)`.
- **`createVerificationCodeHandler` refactored** to pass `apiUrl` as a
6th positional arg to the user's handler, so MFA/OTP/passkey sign-in
flows get the same per-request `iss` as the rest. The other 8 callers
(password-reset, contact-channels-verify, etc.) don't need changes —
they accept fewer args and TS function-arity compatibility makes that
fine.
### SDK — freeze `@stackframe/*` defaults at stack-auth.com
-
[`packages/template/src/lib/stack-app/apps/implementations/common.ts`](packages/template/src/lib/stack-app/apps/implementations/common.ts)
reverts `defaultBaseUrl` and `defaultAnalyticsBaseUrl` to
`https://api.stack-auth.com` / `https://r.stack-auth.com`. A customer
who upgrades their `@stackframe/*` package to the latest version without
explicitly migrating to `@hexclave/*` keeps hitting `api.stack-auth.com`
and never sees their JWT `iss` or OAuth `redirect_uri` change.
- The `@hexclave/*` mirror packages are unaffected because they were
published from source when `defaultBaseUrl =
"https://api.hexclave.com"`; v1.0.0 already targets the hexclave host on
npm. Extending `scripts/rewrite-packages-to-hexclave.ts` to substitute
these literals during future republishes is a separate follow-up.
### Docs — migration guide rewrite
- [`docs-mintlify/migration.mdx`](docs-mintlify/migration.mdx) rewritten
concisely. Spells out the two host-visible changes that require
pre-deploy action when migrating to `@hexclave/*`: updating manual JWT
verifier code (with the array-of-issuers pattern) and updating OAuth
callback URLs at each provider (with the GitHub-OAuth-Apps single-URL
caveat explicitly called out).
## What was deliberately left out
- **Rewriter pipeline extension** for `@hexclave/*` republishes —
separate follow-up.
- **Cross-SDK defaults** (Swift, stack-cli, init-stack still default to
`api.hexclave.com`) — out of scope per discussion.
- **Dashboard launch-checklist host-awareness** — out of scope per
discussion (callback URLs stay hexclave-branded in the dashboard UI).
## Verification
- `pnpm typecheck` — 29/29 packages pass.
- `pnpm lint` — 29/29 packages pass.
- Five parallel review agents (JWT, OAuth, helper, migration guide, SDK
defaults) + an external review of the commit caught four real issues —
all resolved in the same commit before push:
- Missing `api.staging.*` entries in `issuerHostAliases` (would have
broken cross-host token validation on staging).
- Stale comment in `apps/backend/src/stack.tsx`.
- Misleading "backward-compat" comment in `getHardcodedFallbackUrls`.
- MFA/OTP/passkey using env-var fallback for `iss` instead of the
request host.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Makes JWT issuers and OAuth redirect_uri values follow the incoming
request’s host so tokens and redirects always match `api.stack-auth.com`
or `api.hexclave.com`. Also freezes `@stackframe/*` SDK defaults to
Stack Auth and tightens the migration guide to focus on OAuth callbacks.
- **New Features**
- Added `request-api-url` helper with an allowlist of cloud hosts;
unknown hosts fall back to the deployment’s API URL.
- JWT `iss` now uses the per-request API URL; validation accepts both
brands via aliases derived from one `CLOUD_HOST_PAIRS` source.
- All OAuth providers build `redirect_uri` from the request host;
`getProvider()` now takes `{ apiUrl }`.
- Replaced the OAuth2Server singleton with per-request
`createOAuthServer({ apiUrl })` so token exchange mints with the right
issuer.
- Updated token-minting and OAuth routes to pass the API URL;
verification-code flows receive it; connected-accounts refresh paths
safely pin the deployment default.
- SDK: `@stackframe/*` defaults point to `https://api.stack-auth.com`;
`@hexclave/*` mirrors remain hexclave-branded.
- Shared: updated fallback API host lists to include both stack-auth and
hexclave hosts.
- **Migration**
- Update each provider’s OAuth callback URL to
`https://api.hexclave.com/api/v1/auth/oauth/callback/<provider>` when
migrating to `@hexclave/*`.
- If you verify JWTs manually, update the expected issuer to the
hexclave host (including anonymous/restricted variants).
<sup>Written for commit e42dfaf05b.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1498?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Per-request API host awareness so tokens and OAuth flows reflect the
incoming request’s canonical API URL.
* **Updates**
* OAuth providers and servers now derive callback/redirect URLs from
request context rather than a static URL.
* Token issuance/validation now embeds and accepts host-specific issuer
URLs and paired host aliases for rebrand compatibility.
* **Documentation**
* Migration guide updated for branding, JWT issuer expectations, and
OAuth callback guidance.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1498?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
81 lines
3.0 KiB
Plaintext
81 lines
3.0 KiB
Plaintext
---
|
|
title: Migrating from Stack Auth to Hexclave
|
|
description: How to migrate your application from the legacy @stackframe/* packages to @hexclave/*.
|
|
sidebarTitle: Migration
|
|
---
|
|
|
|
Stack Auth is now Hexclave. The dashboard, APIs, and your data are unchanged — only the brand and the package names changed. Both backends (`api.stack-auth.com` and `api.hexclave.com`) point at the same service, so each `@stackframe/*` package continues to work; staying on the legacy SDK requires no action.
|
|
|
|
This guide is for projects that want to migrate to `@hexclave/*` packages.
|
|
|
|
## 1. Install the new packages
|
|
|
|
Replace each `@stackframe/*` dependency with its `@hexclave/*` equivalent:
|
|
|
|
```bash
|
|
npm uninstall @stackframe/stack
|
|
npm install @hexclave/next
|
|
```
|
|
|
|
| Old package | New package |
|
|
| --- | --- |
|
|
| `@stackframe/stack` | `@hexclave/next` |
|
|
| `@stackframe/react` | `@hexclave/react` |
|
|
| `@stackframe/js` | `@hexclave/js` |
|
|
|
|
Rename your imports from `@stackframe/*` to `@hexclave/*`. The public API surface is identical, except that all `Stack*` references are renamed to `Hexclave*`:
|
|
|
|
```ts title="Before"
|
|
import { StackClientApp, StackProvider, useStackApp } from "@stackframe/stack";
|
|
```
|
|
|
|
```ts title="After"
|
|
import { HexclaveClientApp, HexclaveProvider, useHexclaveApp } from "@hexclave/next";
|
|
```
|
|
|
|
## 2. Update your OAuth callback URLs
|
|
|
|
After migrating, Hexclave sends providers (Google, GitHub, Microsoft, Apple, Facebook, Discord, LinkedIn, GitLab, Bitbucket, Spotify, Twitch, X) a `redirect_uri` on `api.hexclave.com`. Update each enabled provider's OAuth app callback URL to:
|
|
|
|
```
|
|
https://api.hexclave.com/api/v1/auth/oauth/callback/<provider>
|
|
```
|
|
|
|
## 3. Update hardcoded references
|
|
|
|
Sweep your codebase and replace:
|
|
|
|
- `https://api.stack-auth.com` → `https://api.hexclave.com`
|
|
|
|
## Optional changes
|
|
|
|
All legacy names keep working — rename only if you want your code to match the new brand.
|
|
|
|
- **Request headers**: `X-Stack-*` → `X-Hexclave-*`.
|
|
- **Environment variables**: `STACK_*` → `HEXCLAVE_*`.
|
|
- **Bearer prefix**: `stackauth_*` tokens remain valid.
|
|
- **CLI binary**: both `stack` and `hexclave` ship with `@hexclave/cli`.
|
|
- **Hosted-handler subdomain**: `.built-with-stack-auth.com` still works.
|
|
|
|
## Other
|
|
|
|
If your backend verifies Hexclave-issued JWTs directly (for example with `jose.jwtVerify`), update the expected `iss` claim — `@hexclave/*` SDKs sign tokens under the hexclave host:
|
|
|
|
```ts title="Before"
|
|
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
issuer: 'https://api.stack-auth.com/api/v1/projects/YOUR_PROJECT_ID',
|
|
audience: 'YOUR_PROJECT_ID',
|
|
});
|
|
```
|
|
|
|
```ts title="After"
|
|
const { payload } = await jose.jwtVerify(token, jwks, {
|
|
issuer: 'https://api.hexclave.com/api/v1/projects/YOUR_PROJECT_ID',
|
|
audience: 'YOUR_PROJECT_ID',
|
|
});
|
|
```
|
|
|
|
The same applies to the anonymous (`/api/v1/projects-anonymous-users/...`) and restricted (`/api/v1/projects-restricted-users/...`) issuer variants.
|
|
|
|
Questions? [Discord](https://discord.hexclave.com) or [team@hexclave.com](mailto:team@hexclave.com).
|