How to add multi-tenant authentication to your TanStack Start SaaS
Most multi-tenancy guides cover the database. This one covers the auth layer: org-scoped sessions, invitation flows, organization switching, per-tenant SSO, and why server functions, not routes, are where tenant checks actually have to live.
Building a SaaS that serves multiple organizations is not just a data architecture problem. It is an auth architecture problem.
Most multi-tenant guides focus on the database layer: shared tables with an organizationId column, separate schemas per tenant, or Postgres row-level security. That layer matters, but it depends on the auth layer getting the tenant context right first. If your session does not carry the correct organization, your database queries filter on the wrong tenant. If your server functions do not independently enforce organization membership, cross-tenant data access becomes a matter of calling the wrong RPC endpoint with the wrong ID.
That last point matters more in TanStack Start than in most frameworks, and it's worth stating up front rather than burying it: TanStack Start's own SDK documentation is explicit that server functions, not routes, are the real security boundary. A route's beforeLoad guard controls what a user sees. It does not control what a createServerFn will execute if called directly, because every server function is also an independently reachable RPC endpoint. This guide covers the auth layer with that distinction in mind: how organization context flows through TanStack Start sessions, how to build an invitation flow, how org switching works (this is one of the few frameworks where WorkOS ships it as a first-class function), and how to scope every server function, not just every page, to the right tenant.
The core concept: Org-scoped sessions, and where they actually get checked
In a single-tenant app, a session carries a user identity. In a multi-tenant app, a session carries a user identity and an active organization. Every authenticated request answers two questions: who is this user, and which organization are they acting as a member of right now.
The active organization determines:
- Which data the user can see
- Which role and permissions they have (a user can be admin in one org and viewer in another)
- Which authentication requirements apply (one org might require SSO, another allows social login)
- Which MFA policy is enforced
In most React frameworks, the natural instinct is to check all of this once, in a layout loader or a route guard, and trust that the rest of the page tree inherits the check. TanStack Start breaks that instinct on purpose. createServerFn functions can be called from route loaders, from other server functions, and from server route handlers, but they can also be called directly over the network as their own RPC endpoint, independent of whichever route rendered the button that triggered them. A beforeLoad hook that checks getAuth() protects the page experience. It does not stop a request from hitting the underlying server function directly with a different organizationId or resource ID in the payload.
The practical implication: every server function that reads or writes tenant data needs its own getAuth() call and its own organization check, not just the route that renders its trigger button. We'll come back to this with a concrete before-and-after example later in the guide.
Setting up WorkOS AuthKit for TanStack Start
WorkOS AuthKit handles the multi-tenant auth layer. Organizations, org membership, invitations, org-scoped roles and permissions, per-tenant SSO configuration, and cookie-based session management are all built in. Your TanStack Start application reads organization context from the session and enforces it inside each server function.
Option 1: The AI installer
The fastest path is the WorkOS CLI. One command detects your TanStack Start setup, installs the SDK, configures your WorkOS dashboard, creates the callback and sign-in routes, writes environment variables, and validates the build:
That's it for the basics. The sections below explain the manual setup and how to extend it for multi-tenancy.
Option 2: Manual setup
Configure middleware
Create or update src/start.ts:
This is the setup step worth reading twice. TanStack Start applies CSRF protection to server functions automatically, but only when your app doesn't define its own startInstance. Registering authkitMiddleware means you now have one, which silently opts you out of that default protection. createCsrfMiddleware restores it. It's a pure header check (Sec-Fetch-Site, Origin, Referer) with no tokens and no interaction with the AuthKit session cookie, and it needs to run before authkitMiddleware so cross-site requests are rejected before any session work happens. Skipping this is an easy way to end up with an app that has authentication but not CSRF protection on its mutation endpoints.
Callback route
Make sure this route matches WORKOS_REDIRECT_URI exactly.
Sign-in endpoint
Set this route as the Sign-in endpoint in the WorkOS dashboard Redirects page. Without it, WorkOS-initiated flows like dashboard impersonation fail the PKCE and CSRF verification this library enforces on every callback.
Reading the session
You don't need a client-side provider unless you want reactive client hooks. For server-rendered pages, getAuth() in a loader is enough:
If you do want reactive client-side auth state (a header that updates the moment a user signs out in another tab, for example), wrap the root route with the provider:
getAuth() and the client hooks are imported from different subpaths on purpose: server functions from @workos/authkit-tanstack-react-start, client hooks from @workos/authkit-tanstack-react-start/client. Mixing them up produces confusing bundler errors rather than a clean type error, so it's worth double-checking imports when something doesn't compile.
The data model: Organizations and memberships
Before building the auth flows, establish the data model. WorkOS manages the authoritative record of organizations and memberships. Your database mirrors what you need for your product (project ownership, billing, settings) while WorkOS handles the auth-facing records.
Mutations in TanStack Start are typically written as server functions with createServerFn, called directly from client components like ordinary async functions. Creating an organization is a good first example, and it's also the first place the "check inside the function, not just the page" rule applies:
Invitation flow
The invitation flow converts a prospective team member's email into an authenticated organization member. It has four steps: an admin sends an invite, WorkOS sends an email with a link, the invitee clicks the link and authenticates, and WorkOS converts the invitation into an organization membership.
Sending and revoking invitations
The members page
Reads happen in the route loader; writes go through the server functions above:
Organization switching
This is the one area where TanStack Start's SDK is ahead of some of its siblings: switchToOrganization is a first-class, documented server function, not something you have to build yourself.
From a client component, the useAuth() hook exposes the same capability reactively:
If the target organization requires SSO that the user hasn't authenticated through yet, the switch will need to route through re-authentication rather than completing silently. Test this path specifically for users who belong to orgs with different SSO requirements, since it's easy to only test switching between two orgs that both use plain email and password.
Per-tenant SSO enforcement
For enterprise customers who want their entire team to sign in through their corporate identity provider, WorkOS lets you configure SSO on a per-organization basis. Once configured, members of that organization who try to sign in with email and password or social login are redirected to the org's SSO provider instead.
You configure the SSO connection in the WorkOS dashboard under the organization's settings, or programmatically via the API. The customer's IT admin can manage the IdP configuration through the WorkOS Admin Portal without your team being involved.
For domain-based SSO routing, where users from acme.com are automatically routed to Acme's SSO provider, enable domain verification for the organization:
From your application's perspective, SSO users and password users look identical. The user object from getAuth() has the same shape regardless of how the user authenticated. organizationId in the session is always the org the user authenticated into.
Scoping server functions to the active tenant, not just the route
This is worth its own section rather than folding it into the invitation and project examples above, because it's the single most important habit to build for this framework.
Here's a vulnerable version of a delete-project function. It looks reasonable, because the page that renders its trigger button is behind _authenticated and only shows projects belonging to the current org:
The problem is that deleteProject is reachable as its own RPC endpoint. Its route's beforeLoad guard never runs, because nothing about calling a server function directly requires loading the page it's normally called from. An authenticated user from a different organization who knows or guesses a projectId can call this function with that ID and delete a resource they were never authorized to touch.
The fix is to make every server function independently responsible for its own authorization, exactly as you would for a public API endpoint, because that is effectively what it is:
Return Not found rather than Forbidden when the resource exists but belongs to a different organization. Forbidden confirms the resource exists, which leaks information about other tenants.
beforeLoad and route layouts are still useful. They give users a coherent experience: redirecting them to sign-in, hiding navigation they can't use, avoiding a flash of content they're not authorized to see. They are UX, not enforcement. Enforcement is whatever runs inside the server function itself, every time, regardless of how it was called.
Org-scoped RBAC
WorkOS RBAC is organization-scoped by default. A user's role and permissions returned by getAuth() reflect their membership in the active organization specifically. Switching organizations updates them automatically.
A small helper keeps the pattern from the previous section consistent across every server function:
Handling users with no organization
New users who sign up without an invitation land in your app with a valid session but no active organization. Handle this state explicitly: either prompt them to create an organization or invite them to join one.
Syncing WorkOS events with webhooks
WorkOS emits events for organization lifecycle changes that your application should respond to: memberships created or revoked, invitations accepted, SSO connections changed, directory sync events. A webhook endpoint is a server route handler, the same shape as the callback and sign-in routes above, just listening for POST:
A webhook route is a plain HTTP endpoint, not a server function, so the CSRF middleware from the setup step doesn't apply to it and doesn't need to. Signature verification is what protects it instead.
Directory Sync is the enterprise feature that replaces manual invitation management for large organizations. When a customer connects their Okta, Azure AD, or Google Workspace directory, WorkOS provisions and deprovisions users automatically as their IT admin adds and removes people. Your webhook handler keeps your database in sync.
Production checklist
Session and middleware
- Pair
authkitMiddleware()withcreateCsrfMiddlewareinsrc/start.ts. Registering a customstartInstancesilently disables TanStack Start's default CSRF protection for server functions unless you add it back. - Configure a sign-in endpoint in the WorkOS dashboard, or dashboard-initiated impersonation will fail PKCE and CSRF verification.
- Set
WORKOS_COOKIE_PASSWORDto a securely generated 32+ character string, unique per environment. - Keep server (
@workos/authkit-tanstack-react-start) and client (@workos/authkit-tanstack-react-start/client) imports separate. Mixing them produces confusing bundler errors.
Data access and tenant isolation
- Never trust a route guard alone. Every server function that touches tenant data needs its own
getAuth()call and its own organization check, because it is independently reachable as an RPC endpoint. - Validate that every resource a server function reads or writes belongs to the authenticated
organizationIdbefore operating on it. - Return
Not foundfor cross-tenant access attempts, notForbidden. Do not confirm that a resource exists for a different tenant.
Invitations and membership
- Set invitation expiry to a reasonable window (5 to 7 days).
- List pending invitations on the members page so admins can see what is outstanding and revoke invitations sent in error.
- Handle the no-organization state for users who sign up without an invitation. Redirect them to onboarding rather than rendering a broken dashboard.
Org switching
- Test switching for users who belong to orgs with different SSO requirements, not just orgs that share the same authentication method.
- After a switch, confirm downstream server functions are reading the updated
organizationIdrather than a value cached from before the switch.
Webhooks
- Verify the WorkOS webhook signature before processing any event. Reject events with invalid signatures immediately.
- Handle
dsync.user.deletedevents to deprovision users as soon as their IT admin removes them from the directory.
Conclusion
Multi-tenant authentication in TanStack Start comes down to the same two questions as any other framework: where does org context come from, and where is it enforced. The answer to the first is the session, read through getAuth(). The answer to the second is different here than it is elsewhere: enforcement belongs inside every server function that touches tenant data, not in a route guard that a direct RPC call can simply route around.
WorkOS handles the complexity that sits above that boundary: organization management, invitation flows, per-tenant SSO, Directory Sync for enterprise customers, org-scoped RBAC, and, unlike some of its other framework SDKs, first-class organization switching out of the box. Your application reads the resulting session inside each server function and enforces it there, every time.
WorkOS is free up to 1 million monthly active users, with no additional charge for organization management, invitations, or RBAC.
Sign up for WorkOS and add multi-tenant authentication to your TanStack Start application.