stack/docs-mintlify/guides/other/tutorials/ship-production-ready-auth.mdx
BilalG1 c14a9dd3d0
feat(hexclave): PR 5 — internal symbol/path/package renames + brand strings (#1547)
## Stack Auth → Hexclave rename — PR 5 (internal symbols, paths,
packages, brand strings)

PR 5 finishes the **internal / non-wire** half of the Stack→Hexclave
rename. It only touches things where nothing outside the repo depends on
the exact name: internal symbols, file/dir names, the
`@stackframe/template` package, and residual brand strings. Plan +
progress are in `HEXCLAVE-RENAME-PR5-PLAN.md`.

Every step was verified green (`pnpm typecheck` + `pnpm lint`, 28/28)
and committed as its own checkpoint, then a fan-out of review agents
audited all commits and the findings were fixed.

### What changed
- **Internal symbols** (`@hexclave/shared`, `packages/template`, apps):
`stack*`/`Stack*` → `hexclave*`/`Hexclave*` — incl.
`stackGlobalsSymbol`, the `_Stack*AppImpl` classes,
`stackAppInternalsSymbol`, `StackContext`, `getStackStripe`, etc. The
`stack*App` local-variable convention
(`stackServerApp`/`stackClientApp`/…) was renamed across 175
source/example/doc files.
- **File renames**: `hexclave-handler/provider/context.tsx`,
`backend/hexclave.tsx`, `internal-tool/hexclave.ts`,
`hexclave-app-internals.ts`.
- **Directory renames**: `lib/hexclave-app`, `hexclave-companion`,
`[...hexclave]` route segment, `skills/hexclave`,
`dashboard/src/hexclave`, and the package dirs
**`packages/{next,shared,ui,sc,cli}`** (dropping the `stack-` prefix to
match the `@hexclave/*` npm names).
- **Packages**: `@stackframe/template` → `@hexclave/template`; **deleted
`packages/init-stack`** (onboarding lives in `@hexclave/cli init`; the
published npm package is untouched).
- **Brand strings**: reworded `Stack Auth`/`Stack dashboard` prose in
code + docs-mintlify, renamed `hexclave-app.mdx`/`use-hexclave-app.mdx`
with redirects, regenerated OpenAPI, updated coupled e2e assertions;
`doctor`/`init` now prefer `hexclave.config.ts`.

### Intentionally kept (verified, not oversights)
Wire/compat identifiers (`x-stack-*` headers, `stack-*` cookies,
`STACK_*` env names, `*.stack-auth.com`, `stackauth_`, `ask_stack_auth`,
query params), public `Stack*` SDK aliases, crypto/JWT/vault
domain-separation tags, `*-brand-sentinel`s, the
`Symbol.for("StackAuth--…")` string, `_stack_sync_metadata`, Postgres
`stackframe` / docker image names, the `stack-auth-logo*.svg` (used by
the rebrand modal), and `migration.mdx` / "formerly known as Stack Auth"
notes. False positives (Phosphor `StackIcon`/`StackSimple`, `TanStack`,
`OrbStack`, `stackable`/`Stacked` charts) left alone.

### Review pass
Six review agents audited all commits. Found + fixed one real bug — a
build script (`bundle-type-definitions.ts`) hardcoded the old
`lib/stack-app` glob path (not an import, so typecheck/lint were blind),
silently emptying the dashboard AI type bundle — plus stale comments, a
dead CI env var, and stale `.gitignore`/`.dockerignore` entries.
Cross-cutting audit confirmed **zero wire-compat identifiers were
accidentally renamed**.

### ⚠️ Verification note
`typecheck` + `lint` are fully green locally. The **e2e suite was not
run** (needs a live backend+DB), so the brand-string assertion +
OpenAPI-regen changes are verified by grep/codegen only — please let CI
exercise e2e to confirm.

### Base-branch note
This branch was forked from the local-only `cl/friendly-lewin-72293f`
(not on origin, no separate PR), so this PR against `dev` also carries
that branch's ~11 preceding Hexclave-rename commits (config-file rename,
env-var dual-read, AI setup-prompt rebrand). If those should land
separately, re-parent before merge.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Finishes the internal Stack Auth → Hexclave rename and cleans up
remaining stragglers, including dev-tool and prompt copy. All changes
are internal-only; public/wire APIs remain unchanged. Re-merged `dev`
and resolved the payments create-purchase-url conflict.

- **Refactors**
- Internal symbols: stack*/Stack* → hexclave*/Hexclave* (e.g.,
`getHexclaveServerApp` via `@/hexclave`, `getHexclaveStripe`,
`hexclaveAppInternalsSymbol`, `hexclaveSchemaInfo`, Prisma
`__hexclave_*`, `data-hexclave-handler-page`, Stripe mock
`hexclavePortPrefix`).
- Files/dirs: moved to `lib/hexclave-app`; handler route
`[...hexclave]`; backend entry `src/hexclave.tsx`; dashboard internals
`hexclave-app-internals`; companion `hexclave-companion`; dropped
`stack-` prefix across package dirs
(`packages/{shared,ui,sc,cli,next}`); workflows/emulator paths now
`packages/cli`; Quetzal codegen env at `packages/next/.env.local`.
- Packages/docs: `@stackframe/template` → `@hexclave/template`; removed
`packages/init-stack`; regenerated OpenAPI and updated docs
slugs/redirects for hexclave-app/use-hexclave-app.
- Brand strings/prompts: reworded remaining “Stack” dashboard strings to
Hexclave; updated dev-tool copy and prompts; `doctor/init` now prefer
`hexclave.config.ts`. Kept all wire-compat identifiers and public
aliases (`x-stack-*`, `stack-*` cookies, `STACK_*` env,
`*.stack-auth.com`, `Stack*` SDK names).
- Rebased/merged onto latest `dev`: retained `@hexclave/template`, kept
`src` in published files, refreshed setup-prompt imports and docs JSON,
adopted 1.0.5 version bumps, and re-merged `dev` again (resolved
`create-purchase-url` with `getHexclaveStripe`).

- **Bug Fixes**
- Restored dashboard AI type bundle by pointing the glob to
`packages/template/src/lib/hexclave-app`.
- Addressed rename leftovers: updated lingering `@/stack` imports and
CSS selector, fixed schema/meta and port-prefix expansions, and aligned
emulator commands to `packages/cli`.
- CI/build: removed a dead env var and stale ignore entries; fixed
Docker by renaming `STACK_SKIP_TEMPLATE_GENERATION` →
`HEXCLAVE_SKIP_TEMPLATE_GENERATION`.

<sup>Written for commit 3c1af3bff3.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1547?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-06-03 18:57:09 -07:00

178 lines
10 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Ship Production-Ready Auth
description: Lock down page and API access, handle secrets and environments, then align domains, OAuth, email, webhooks, and dashboard production mode with Hexclave.
---
Going live is not only “turning on production mode.” This guide focuses on **what must be true** so only signed-in users reach protected surfaces, **secrets stay server-only**, and Stacks dev-friendly defaults are replaced with **your** domains, OAuth apps, and email.
If you are still wiring Stack into your app, complete [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave) first. For team RBAC before launch, see [Build a team-based app](/guides/other/tutorials/build-a-team-based-app).
## What you will have at the end
- A clear model for **protecting pages**, layouts, route handlers, and server actions—and when to use middleware vs in-render checks.
- Awareness of **Next.js + sensitive HTML** so you do not leak data through composition.
- A **secrets and environment** split that keeps the server key out of the browser.
- A **launch-aligned** checklist (domains, OAuth, email, production mode) with pointers to deeper docs.
- A **webhook** verification mindset (signed payloads only).
## 1. Protect a page (and know what that actually guarantees)
You typically combine **one or more** of:
1. **Middleware** — cheap gate when the URL alone tells you the area is private (for example everything under `/app`).
2. **Server Components / server loaders** — `await hexclaveServerApp.getUser({ or: "redirect" })` (or handle `null`) on the route that renders sensitive UI.
3. **Client Components** — `useUser({ or: "redirect" })` for UX; **never** the only layer for authorization.
<Tabs>
<Tab title="Middleware (route prefix)">
```tsx title="middleware.ts"
import { NextRequest, NextResponse } from "next/server";
import { hexclaveServerApp } from "@/stack/server";
export async function middleware(request: NextRequest) {
const user = await hexclaveServerApp.getUser();
if (!user) {
return NextResponse.redirect(new URL("/handler/sign-in", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: "/app/:path*",
};
```
Match **only** prefixes that should be gated. Do **not** blanket-match `/` or you can block static assets and Stacks **`/handler`** routes (sign-in, sign-up, callbacks).
If your project uses **custom handler base paths**, use the sign-in URL from `hexclaveServerApp.urls.signIn` as the redirect target instead of hard-coding `/handler/sign-in` (it may be an absolute URL depending on configuration—`NextResponse.redirect` accepts that).
</Tab>
<Tab title="Server Component">
```tsx title="app/app/dashboard/page.tsx"
import { hexclaveServerApp } from "@/stack/server";
export default async function DashboardPage() {
await hexclaveServerApp.getUser({ or: "redirect" });
return <h1>Dashboard</h1>;
}
```
</Tab>
<Tab title="Client Component (UX gate)">
```tsx title="app/app/settings/client-gate.tsx"
"use client";
import { useUser } from "@hexclave/next";
export function SettingsGate({ children }: { children: React.ReactNode }) {
useUser({ or: "redirect" });
return <>{children}</>;
}
```
</Tab>
</Tabs>
### `redirect` vs `throw`
- **`{ or: "redirect" }`** — use when a browser navigation is appropriate (pages, most Server Components).
- **`{ or: "throw" }`** — use in **server actions**, **route handlers**, and other places where redirecting would be wrong; map errors to `401`/`403` responses yourself.
```tsx title="app/api/me/route.ts"
import { hexclaveServerApp } from "@/stack/server";
import { NextResponse } from "next/server";
export async function GET() {
try {
const user = await hexclaveServerApp.getUser({ or: "throw" });
return NextResponse.json({ id: user.id });
} catch {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
```
### Sensitive content and Next.js composition
Authentication prevents **impersonation**, but **HTML composition** still matters:
- **Client Components** ship to the browser; gating with hooks does not hide their bundled code.
- **Server Components**: a protected parent does **not** guarantee an unprotected child (or an unprotected segment under a layout) never reaches the client. Anything that embeds **secrets or PII** should **itself** call `getUser({ or: "redirect" })` / `getUser({ or: "throw" })` (or otherwise avoid rendering that data when unauthenticated).
- **Middleware**: on **Next.js 15.2.3+**, middleware-only protection does not leak unprotected server-rendered fragments the way older versions could; still treat **authorization** (who may perform an action) as a **server** concern.
Read the full discussion in [User fundamentals — Protecting a page](/guides/getting-started/user-fundamentals#protecting-a-page).
<Info>
Treat **client-side** checks as **UX only**. Anything that mutates data or exposes another customers data must be enforced on the server (Server Components, server actions, route handlers, or your backend with the **secret server key** or validated tokens). For team-scoped apps, re-check [RBAC](/guides/apps/rbac/overview) on the server, not only with `usePermission`.
</Info>
## 2. Secrets, keys, and environments
<Warning>
**`STACK_SECRET_SERVER_KEY`** (or `ssk_...`) must **only** exist in server-side environments (SSR, route handlers, server actions, your backend). Never prefix it with `NEXT_PUBLIC_`, never import it from code that runs in the browser, and never log it. See the [HexclaveApp SDK reference](/sdk/objects/hexclave-app) and the [REST API overview](/api/overview).
</Warning>
Practical split:
| Variable | Typical exposure | Use |
|----------|------------------|-----|
| `NEXT_PUBLIC_STACK_PROJECT_ID` | Browser + server | Identifies the project to the hosted UI and client SDK. |
| `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY` (if used) | Browser + server | Client-safe key where your project uses publishable keys. |
| `STACK_SECRET_SERVER_KEY` | **Server only** | Elevated server SDK and REST **server** API. |
Use **separate** Stack projects or at least **separate** env values for production vs staging when possible. Rotate keys from the dashboard if a secret is exposed.
## 3. Domains, OAuth, email, and production mode
Stacks dev defaults (localhost callbacks, shared OAuth keys, shared mail) are convenient but **not** what you want for real users.
<Steps>
<Step title="Domains and callbacks">
Add your real **`https://…` origin** under **Domain & Handlers** and disable **Allow all localhost callbacks** when you no longer need local redirects against production configuration. Details: [Launch checklist — Domains](/guides/apps/launch-checklist/overview#domains).
</Step>
<Step title="OAuth providers">
Create **your own** OAuth clients per provider, set the provider callback URLs Stack documents, then paste **your** client ID and secret in the dashboard (leave shared keys for local dev only). Details: [Launch checklist — OAuth providers](/guides/apps/launch-checklist/overview#oauth-providers) and [Auth providers](/guides/apps/authentication/auth-providers).
</Step>
<Step title="Email">
Point outbound mail at **your** SMTP/domain so magic links and invitations come from a domain users trust. Details: [Launch checklist — Email server](/guides/apps/launch-checklist/overview#email-server) and [Emails](/guides/apps/emails/overview).
</Step>
<Step title="Enable production mode">
After the above, turn on **production mode** in **Project Settings** so dashboard guardrails match how you run in prod ([Launch checklist — Enabling production mode](/guides/apps/launch-checklist/overview#enabling-production-mode)).
</Step>
</Steps>
## 4. Webhooks
If you consume Stack webhooks, **verify every payload** (for example with Svix and `STACK_WEBHOOK_SECRET`) before acting on events—treat unsigned or failed verification as `400`. Implementation patterns: [Webhooks](/guides/apps/webhooks/overview).
## 5. Before you flip traffic
- **Smoke-test** sign-in, sign-up, password reset, and OAuth **on the production domain** after DNS and env vars are final.
- **Confirm** redirect URLs in third-party consoles (OAuth, IdP) match the Stack callback URLs you use in prod.
- **Align** session / cookie behavior with your hosting (same-site, HTTPS, reverse proxies) per your platform docs.
- **Plan rollback**: keep prior env values or a maintenance window note so you can revert dashboard or env changes quickly.
## Related guides
| Topic | Guide |
|--------|--------|
| First integration | [Build a SaaS with Hexclave](/guides/other/tutorials/build-a-saas-with-hexclave) |
| Page protection details | [User fundamentals](/guides/getting-started/user-fundamentals) |
| Domains, OAuth, email, prod mode | [Launch checklist](/guides/apps/launch-checklist/overview) |
| `HexclaveServerApp` and keys | [HexclaveApp SDK reference](/sdk/objects/hexclave-app) |
| Webhook verification | [Webhooks](/guides/apps/webhooks/overview) |
| REST from your backend | [API overview](/api/overview) |
## FAQ
<AccordionGroup>
<Accordion title="Is middleware enough to protect my API?">
Middleware runs on **matching routes** and is great for coarse “must be logged in” gates. **Route handlers** and **server actions** should still call `getUser({ or: "throw" })` (or equivalent) and return proper HTTP errors—middleware will not run for every internal call path, and **authorization** (what that user may do) belongs next to your business logic.
</Accordion>
<Accordion title="Should production and development share one Stack project?">
Prefer **separate projects** (or strictly separated keys and OAuth clients) so you never point production OAuth callbacks at localhost, and so a leaked dev key cannot touch production data.
</Accordion>
<Accordion title="Where do teams and RBAC fit for launch?">
Model permissions in the dashboard, then enforce them on the server for every sensitive operation. Walkthrough: [Build a team-based app](/guides/other/tutorials/build-a-team-based-app).
</Accordion>
</AccordionGroup>