6.2 KiB
name, description, metadata, user-invocable
| name | description | metadata | user-invocable | ||
|---|---|---|---|---|---|
| auth | Add user accounts and sign-in to this TanStack Start app. Use when the app needs authentication, sign-in, user accounts, protected routes, or per-user data. Triggers on "auth", "login", "log in", "sign in", "sign up", "account", "users", "authentication", "protected", "who is logged in", "current user", "per-user". |
|
false |
Auth
This app runs its own Better Auth at
/api/auth/*, federating to the shared Grok auth broker (auth.grok.me)
via the genericOAuth plugin. This template wires Google and X.
Supported sign-in methods — use ONLY these three; nothing else is supported:
Google, X, and email/password. No other social/OAuth provider (GitHub, Apple,
Discord, …), no magic links, passkeys, OTP, phone/SMS, or anonymous sign-in. Do
not add entries to GROK_PROVIDERS. Method detail and the email/password switch
(edit only src/lib/auth/email-password.ts): references/sign-in-methods.md.
Exception — gate viewers are signed in already; NEVER render login/re-auth
buttons for them. Connector / app-data apps: ONLY gate "Continue with Grok",
no Google/X buttons: references/grok-identity.md.
Sign-in is OFF by default — the template ships .grok/app-env.json with
{"VITE_AUTH_ENABLED": "false"}, so only add accounts when the ask calls for
them (AGENTS.md §0.5). Switching it on is "Turning sign-in on" below.
Once on, sign-in is REAL — including in the sandbox live preview. Do NOT
scaffold demo/mock/hardcoded users. Preview: popup + baked preview client;
deployed: per-app client + DATABASE_URL + zero-click gate sign-in
(references/prewired-and-env.md).
While OFF (VITE_AUTH_ENABLED=false) a dev user is returned so a
non-auth app renders without a signed-in visitor — dev and preview only.
Deployed, the flag is the platform's (always "true"), so requireUserId
rejects every visitor — auth-off apps use neither it nor authMiddleware.
Everything is preinstalled and pre-wired in src/lib/auth/ — do not
npm install anything; better-auth is the only auth package (never
@neondatabase/*, @stackframe/*, or @clerk/*). Do not edit or rewrite any
file under src/lib/auth/ — server.ts least of all — except
email-password.ts for its one flag. Per-file map:
references/prewired-and-env.md.
/auth/popup is already handled by the template Vite plugin
(vite.config.ts → popup.server.ts): it never paints the React app. Do NOT
create src/routes/auth/popup.tsx (or any React page / client OAuth at that
path) — that shows the full app inside the popup, the common failure mode.
Never write a .env / .env.local / .env.example in this sandbox: live
preview needs zero env configuration and a deployed app gets its vars
injected by the platform. The knobs that exist are in
references/prewired-and-env.md — never expose a non-VITE_ var to the client.
migrations/auth/0001_auth.sql is the Better Auth schema — do not edit. It
sits outside the globbed migrations/ directory (neither applier descends), so
it is not applied to apps without sign-in; "Turning sign-in on" copies it up.
Turning sign-in on
Do all of this — the routes alone render the disabled branch:
- Flag: delete the
VITE_AUTH_ENABLEDkey from.grok/app-env.jsonand restart the dev server. Vite reads env at startup, so HMR will not pick it up.npm run dev,npm run buildandnpm run previewall read that file throughscripts/with-app-env.mjs, so preview and the built output flip together — never start Vite directly. - Schema:
cp migrations/auth/0001_auth.sql migrations/0001_auth.sql, then restart so it applies. It is tracked by basename in_migrations, so a database that already has it will not re-run it. - Routes: add
src/routes/api/auth/$.ts+src/routes/login.tsx— copy both fromreferences/wiring.md(the catch-all API route is what makes/api/auth/*and the broker callback work). - Sign out: a login with no way out is not done — render
<UserButton />from@/lib/auth/gates(wiressignOut(); hides sign-out for gate sessions). - Existing data: wrap the app's server functions in
authMiddleware(an auth-off app must not have been using it — see theneonskill). Rows from before sign-in existed are development data: drop and recreate them unless the user says otherwise — don't hand them to whoever signs in first.
Building on it once it's on
- Sign in / out:
signIn(providerId)andsignOut()from@/lib/auth/client;GROK_PROVIDERSrenders the buttons. The popup, bearer-token hand-off, and request attachment are internal — leave them alone. Prefer<UserButton />(it handles the pending and failure states);signOut()rejects when deployed if the server never confirms — catch it. NeverauthClient.signOut(): it leaves the preview bearer token attached to every later request, so the visitor stays signed in. - Reading the user:
useCurrentUser()is display-only (nullmeans loading OR signed out, so never redirect on it alone); guard onuseCurrentUserState()'sisPendinginstead. Gates (SignedIn,SignedOut,SignInGate,RedirectToSignIn,UserButton) live in@/lib/auth/gates. CTA hard rules, skeleton, and cookie-SSR zero-flash:references/session-ui.md. - Per-user data (mandatory): every server function that touches per-user data
must use the prewired
authMiddlewareand scope every read and write tocontext.userId— a Postgres driver has full DB access, so nothing else limits the query. Keepuser_idcolumnsTEXT; never trust a client-supplied user id; signed out, the middleware throwsUnauthorizedError(401). Code and disabled-mode semantics:references/per-user-data.md. - Security model: headless broker,
__Host-cookies +trustedOrigins, and Fetch-Metadata sibling isolation are already wired — never weaken them to make an error go away (references/sign-in-methods.mdcovers the model and the "Invalid origin" fix).