Skip to main content

Authentication

How sessions, roles and tenancy fit together.

Auth is Better Auth with the organization, admin and twoFactor plugins. The config lives in src/lib/auth.ts.

Two separate role axes

user.role is the platform role — user or admin — and controls access to /admin. member.role is the organisation role — owner, admin or member — and controls what you can do inside one workspace. A platform admin is not automatically an owner of anyone's organisation.

Where authorisation actually happens

The middleware only checks for a session cookie so signed-out visitors don't see the app shell flash. Real checks happen in three places:

  1. src/app/(app)/organizations/[slug]/layout.tsx — confirms membership
  2. Every server action — re-derives session and role from the database
  3. Route handlers — same, before any work is done

A layout does not protect a server action. Actions are separately addressable, so each one calls requireMembership() itself.

Adding a capability

Add it to the map in src/lib/permissions.ts, then call assertCan(role, 'your:capability'). Do not write inline role comparisons — that is how the sidebar and the server end up disagreeing.