Technology handbook
BetterAuth
Framework-agnostic authentication with sessions, plugins, organizations and two-factor support.
- Topics
- 33
- Sections
- 5
Hooks
authClient.useSession
Read the session reactively in a component, with isPending and error alongside the data.
- It returns data, isPending and error together, so all three states are handled at once.
- The component re-renders when the session changes rather than reading a stale snapshot.
- Rendering before isPending clears is what makes a signed-in user flash as signed out.
useSession versus getSession
The hook re-renders on change; the promise is for everywhere a hook may not run.
- The hook subscribes; the promise resolves once and never updates the caller.
- Server code and event handlers cannot call a hook, which is what the promise is for.
- Calling the promise on every render refetches the session instead of reusing it.
Server hooks: before
Run logic before a matched endpoint with createAuthMiddleware, and reject or rewrite the request.
- createAuthMiddleware runs ahead of the matched endpoint and can reject the request outright.
- Rewriting the context here changes what the endpoint receives, not just what it logs.
- Throwing inside it is how a sign-up is blocked before any row is written.
Server hooks: after
Act once an endpoint has succeeded - send a welcome email, record an analytics event.
- It runs only once the endpoint has succeeded, so side effects never fire on a failure.
- Welcome mail and analytics belong here rather than inside the endpoint itself.
- Work done here still delays the response unless it is handed to a queue.
Hook matchers
Match the paths a hook applies to, so it does not run on every auth request.
- A matcher limits a hook to the paths it is about instead of every auth request.
- Without one, a sign-in hook also runs on session reads and token refreshes.
- Matching on path keeps the cost of a hook proportional to how often it is needed.
getSessionFromCtx
Read the caller's session inside a hook without a second round trip.
- It reads the session already resolved for this request rather than fetching it again.
- Using it inside a hook avoids a second database round trip per call.
- It returns null for unauthenticated endpoints, which the hook must handle.
Database hooks
Intercept create and update on user, session and account records, before or after they are written.
- They intercept create and update on user, session and account rows.
- A before hook can refuse or rewrite the write; an after hook only observes it.
- They fire for every path that touches the record, including provider callbacks.
Plugin hooks
Ship a plugin that contributes its own before and after handlers alongside its endpoints.
- A plugin ships its own before and after handlers beside its endpoints.
- Handler order follows plugin registration order, which makes it deterministic.
- One plugin's hook can see the effect of another's, so ordering is a real dependency.
Organization client hooks
Read the active organization and membership lists through the plugin's own hooks.
- The active organization and membership list are read through the plugin's own hooks.
- They re-render on a switch, so the interface never shows the previous tenant's data.
- They resolve only once the plugin is enabled on both server and client.
Core setup
Server instance
Configure the auth server with a database adapter and a base URL.
- The server owns its tables through an adapter and a declared base URL.
- The base URL is what callback and redirect targets are built from.
- Enabled options here decide the schema, so the instance and migrations move together.
Database schema
Generate and apply the tables the enabled features require.
- The generator emits the tables the currently enabled features require.
- Applying the migration before enabling the feature is what keeps sign-in working.
- A schema behind the configuration fails at request time, not at deploy.
Route handler
Mount the handler so client calls reach the server API.
- Mounting the handler is what makes the client's typed calls reach the server API.
- The mount path must match the client's base path or every call 404s.
- One handler serves every enabled plugin's endpoints, not one route per feature.
Client instance
Create a typed client that matches the server's enabled plugins.
- The client is typed from the server's enabled plugins, so missing methods are a compile error.
- Its base URL must point at the mounted handler, not the application root.
- Adding a plugin server-side without the client leaves the methods untyped.
Environment configuration
Set the secret and provider credentials per environment.
- The secret signs sessions, so changing it signs everyone out at once.
- Provider credentials are per environment because each has its own callback URL.
- Configuration read at module load will not pick up a value set later at runtime.
Framework adapters
Integrate with Next.js, SvelteKit, Hono or a plain Node server.
- An adapter translates the framework's request and response into what the handler expects.
- Cookie writing differs per framework, which is most of what the adapter carries.
- Running on the edge restricts the adapter to APIs that runtime actually provides.
Authentication methods
Email and password
Enable credential sign-up with verification and reset flows.
- Verification and reset flows come with the method rather than being written by hand.
- Password rules are configured once and enforced on sign-up and reset alike.
- Enabling it adds credential columns the migration must apply first.
Social providers
Add OAuth providers and map returned profiles onto users.
- The provider's returned profile is mapped onto the user row on first sign-in.
- Each provider needs its redirect URI registered before the callback will return.
- A second provider on the same email links or duplicates depending on configuration.
Magic links
Sign in with a single-use link and a short expiry.
- The link is single use and dies on redemption, not on the next request.
- A short expiry is what limits the damage of a forwarded or logged URL.
- It needs somewhere to store the pending token, so the schema must be current.
Passkeys
Register and authenticate with WebAuthn credentials.
- WebAuthn binds the credential to the origin, so it cannot be replayed elsewhere.
- The private key never leaves the authenticator, which removes the password to steal.
- Registration and authentication are separate ceremonies with separate endpoints.
Two-factor authentication
Add TOTP or OTP as a second step with recovery codes.
- TOTP adds a second step whose codes are time-bound rather than reusable.
- Recovery codes are the fallback, and they must be single-use and stored hashed.
- Enabling it adds tables, so the migration has to land before the feature does.
Anonymous and guest sessions
Let a visitor act before signing up and upgrade the account later.
- A visitor can act before signing up, and the work is kept against a real session.
- Upgrading links the existing session to the new account instead of starting over.
- A guest session still counts as a session, so authorization must check more than presence.
Sessions & accounts
Session management
Read, refresh and revoke sessions server-side.
- Sessions are rows, so revoking one takes effect on the very next request.
- Listing a user's sessions is what makes a sign-out-everywhere control possible.
- Refreshing extends the record rather than issuing an unrelated new one.
Cookie configuration
Set attributes and domains so sessions work across subdomains.
- The cookie domain decides which subdomains can see the session at all.
- sameSite governs whether the session rides along on cross-site navigation.
- Attributes set for local development will not be valid over HTTPS in production.
Account linking
Attach multiple providers to one user deliberately.
- Linking keeps one user row when the same person arrives through a second provider.
- Trusting an unverified provider email is how one account gets taken over by another.
- Unlinking must refuse to remove the only remaining sign-in method.
Email verification
Block privileged actions until an address is confirmed.
- Privileged actions stay blocked until the address is confirmed.
- The token is single use, so a resend must invalidate the previous one.
- Verification state belongs on the user row, not inferred from the provider.
Password reset
Issue single-use reset tokens that expire and cannot be replayed.
- The reset token is single use and expires whether or not it is redeemed.
- Resetting must end existing sessions, or a thief keeps the one they already have.
- Responding identically for unknown addresses is what stops the form enumerating users.
Session hooks
Run logic before or after a sign-in or sign-out.
- They run either side of a sign-in or sign-out rather than on every read.
- A before hook can refuse the sign-in; an after hook can only react to it.
- Audit records written here capture the event with the session still in context.
Plugins & multi-tenancy
Organization plugin
Model teams, memberships and invitations.
- Teams, memberships and invitations arrive as tables plus typed client methods.
- A user's active organization is what every scoped query is filtered by.
- Enabling it changes the schema, so it is a migration before it is a feature.
Roles and permissions
Define access rules and check them on the server.
- Rules are declared once and checked on the server, never only in the interface.
- A role is per membership, so the same user can be an admin of one team and not another.
- A client-side check is a convenience; the server check is the actual boundary.
Admin plugin
Impersonate, ban or manage users with an audited path.
- Impersonation, bans and user management arrive on an audited path rather than ad hoc SQL.
- Every privileged action is attributable, which is what makes it reviewable afterwards.
- The plugin's own endpoints need protecting, or it becomes the way in.
Rate limiting
Throttle sign-in attempts to blunt credential stuffing.
- Throttling sign-in attempts is what makes credential stuffing expensive.
- Limiting by address alone punishes shared networks and misses distributed attempts.
- The counter must be shared across instances or each one enforces its own limit.
Custom plugins
Extend the schema, server API and client together.
- A plugin extends schema, server API and client types as one unit.
- Shipping the three together is what keeps the client's methods honest.
- Its migration is still a migration, and it must land before the plugin is enabled.
Migration planning
Roll out a plugin's schema change before enabling it in production.
- The schema change is deployed and applied before the feature is turned on.
- A plugin enabled ahead of its columns fails at sign-in, not at deploy.
- Rolling back the code without rolling back the schema is the safer order.
Sources
- BetterAuth official documentation