Sessions and auth
Read the session on either side, and add your own fields to it.
On the server
Every procedure gets the session as ctx.session. On protectedProcedure and anything built on it, it's never null:
me: protectedProcedure.query(({ ctx }) => ({
id: ctx.session.user.id,
role: ctx.session.user.role,
})),
Outside tRPC, in a plain Fastify route, read it with getActiveSession from backend/src/auth.ts.
On the client
The web app's auth client is in web-app/src/AuthHelpers/auth-client.ts:
import { authClient } from "./AuthHelpers/auth-client";
const { data: session, isPending } = authClient.useSession();
Sign-in, sign-up, sign-out, and password reset are methods on the same client — authClient.signIn.email, authClient.signOut, and so on. See the Better Auth docs.
What's on the session
| Field | What it is |
|---|---|
user.role | The global role name |
user.viewMode | Light, dark, or follow the system |
user.active | False when an admin has deactivated the account |
enabledFeatures | Every feature the role has, enabled and granted |
hasAcceptedTermsAndConditions | true, false, or null when no terms are active |
avatarUrl | A signed URL for the uploaded avatar, if there is one |
Adding a field
There are two kinds.
A column on the user — add it to user.additionalFields in auth.ts, with input: false so users can't set it themselves. Then regenerate the auth schema and write a migration:
yarn workspace backend db:auth:generate
yarn workspace backend db:generate
A computed value — add it to the customSession plugin in auth.ts. It runs on every session read, so keep it fast.
Both reach the client's types automatically through inferAdditionalFields and customSessionClient.
Sign-in methods
Email and Google can each be switched on or off in Admin → Site Settings. Only email is on in a fresh database. The login page asks authProtocols.list which ones to show.
Who becomes what
The first account ever becomes owner. Every later account starts as demo. To change that, edit nextUserRole in auth.ts — and update PROTECTED_ROLES in trpc/routers/roles.ts to match.
Related
- Auth — what's built, and how it compares.
- Better Auth — why it was chosen.
- Checking permissions — using
enabledFeatures.