Let one system own identity
The login form collects a request. The authentication system decides whether that request establishes an identity and issues the session the rest of the application trusts. Keeping that responsibility in one place prevents each route from inventing its own interpretation of a signed-in user.
supastack composes Better Auth for identity and session handling. Application code resolves the session at protected boundaries and connects it to a workspace. The interface can show helpful access states, but client state must not become evidence that an operation is authorized.
The rule has to hold on every surface the product ships. Web, mobile, and desktop clients should consume the same identity decision rather than implementing it three times — three interpretations of “signed in” is where account bugs come from.
Choose sign-in methods deliberately
Every method trades friction against attack surface and an outside dependency. Passwords need reset flows and rate limiting. Magic links and one-time codes depend on email deliverability. OAuth delegates identity to providers like Google, GitHub, or Apple — and inherits their outages, consent screens, and callback rules.
supastack composes these through Better Auth — email OTP, password, magic link, OAuth, with two-factor and passkeys available — behind a single identity decision. Ship the smallest set your audience actually needs; each additional method is another callback path, another failure mode, and another flow to verify before launch. The authentication guide covers the wiring.
When a person can sign in more than one way, decide how the methods join. A Google account and an email magic link that resolve to the same address should land on one identity, not two. Account-linking rules are part of the identity decision, and getting them wrong creates duplicate users that surface months later as mysteriously missing data.
Sessions are the credential that matters
After sign-in, the session — not the password — is what every request carries. Its expiry, storage, and invalidation rules define the real security posture. A session that survives sign-out, or one a removed workspace member can keep using, is an authentication failure no polished login screen can fix.
Decide the lifecycle up front: how long sessions live, whether users can see and revoke their active sessions, and what a password change, email change, or workspace removal does to them. Session management exists as a module in the kit precisely because these decisions are easy to postpone and expensive to retrofit.
Connect the session to the requested record
Being signed in does not grant access to every record. Resolve the user's workspace on the server and include that scope in reads and mutations. A task identifier or recovery link is a reference to work, not permission to retrieve it — the workspace boundaries post covers that side of the boundary in detail.
Resolve the scope in a fixed order: authenticate the session, load the membership, then read or write with that workspace context — never the other way around. Middleware can redirect an anonymous visitor, but it cannot authorize the operation; the check belongs in the server action or route handler where the record is touched.
Accounts have a lifecycle, not just a beginning
Provisioning is the first test of that boundary. When a user signs up, the workspace and ownership records that follow must be safe to create more than once — lifecycle hooks can be replayed by providers, retries, or support tools. A duplicate event should be a no-op, not a duplicate organization.
The end of the lifecycle deserves the same care. Email changes, password resets, member removal, account deletion, and data export are each small flows with their own abuse cases — a reset link that works twice, an export that leaks a teammate's records. supastack ships GDPR export and session management for this reason; whatever you build on, budget for the boring flows.
Test the lifecycle, including its end
A successful login test is useful, but incomplete. Exercise invalid credentials, expired or reused verification codes, anonymous requests, and cross-workspace access. Verify that sign-out invalidates the session and that a returning client cannot keep using a previously authorized screen to perform protected work.
Distinguish handler tests from deployment acceptance. A memory adapter can establish the semantics of a real authentication handler without proving Postgres persistence or a provider callback. Verify the configured origin, callback path, session restoration, and sign-out with the deployed dependencies before claiming that the complete sign-in journey works.
- Invalid credentials and expired or reused verification codes are rejected.
- Anonymous and cross-workspace requests receive no owned data.
- Sign-out invalidates the session — a stale client cannot keep acting.
- Provisioned records stay idempotent when lifecycle hooks replay.
- Real-provider callbacks work on the configured origin — verified, not assumed.
Common authentication questions
The questions below surface once the login form is done and the system has to keep working.
- Is middleware enough to protect routes? — Middleware is a convenient redirect, not an authorization layer. Each server action and route resolves the session and workspace itself; page-level guards hide screens without authorizing operations.
- Build auth myself or use a library? — Session semantics are rarely where a SaaS differentiates. A maintained library like Better Auth behind clear module boundaries gives you tested primitives while keeping the decisions that matter — scoping, lifecycle — in your code.
- How do sessions relate to workspaces? — The session identifies the person; the workspace identifies the records they may touch. Resolve membership on the server for every request and never trust a workspace ID the client sends.
- What do B2B customers expect from authentication? — Team workspaces with roles, OAuth sign-in, session visibility and revocation, and an audit trail. Two-factor and passkeys are increasingly expected for admin roles.
- What breaks authentication in production? — Usually the edges: a callback URL that only exists locally, a hook that provisions twice, a session that outlives a membership. Test the ends of flows, not just the starts.