Authentication
Authentication lives entirely in the auth service, powered by
BetterAuth. It is the trust anchor: it owns the user,
session, account, and verification tables, issues httpOnly session cookies, and is
the only service permitted to manage identity. Every other service
verifies requests by forwarding cookies to it.
What you configure at init
When you keep the auth service in the wizard, you choose:
| Option | Choices |
|---|---|
| OAuth providers | Google, Apple, GitHub (any combination) |
| Email verification | on / off |
| Password reset | on (default) / off |
| Two-factor authentication | on / off |
| Additional user fields | extra columns on the user record |
| Admin dashboard | on (default) / off |
Email/password is always available. Selecting email verification or password reset wires in transactional email (Nodemailer); in test mode, sending is a no-op.
OAuth setup
OAuth client IDs and secrets are environment variables on the auth service — they
ship as placeholders in .env.example:
# auth/backend/.env
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
APPLE_CLIENT_ID=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...Sessions are httpOnly cookies
BetterAuth issues and rotates httpOnly session cookies — there is no client-held JWT
to leak. A base service reads the principal by calling
/api/auth/get-session; it never decodes a token itself. Native clients that can't carry
cookies use the flexible flavor's
device-session token instead.
The admin dashboard
With the dashboard enabled, the auth service ships a Next.js admin app on port
:3333 — sign in, list users, manage roles, and delete users out of the box. It
doubles as a reference implementation for consuming the auth backend from a stackr web
app.
Roles
User roles drive the role-gated middleware flavor:
a role-gated service admits only users holding one of its allowed roles (with admin
always permitted). You assign roles from the admin dashboard.
Provisioning into a service
A user authenticated against auth may not yet have a record in a given base service.
Each user carries a has<Service>Account flag; on the user's first authenticated call,
a provisioning service creates the local record and flips the flag via
POST /api/auth/provision. See Cross-Service Auth.
Identity has exactly one owner
Only the auth service may declare user, session,
account, or verification tables. This is enforced by
ast-grep and check:auth-tables,
not just convention.