Skip to content
Tech Stack26 July 2026 · 8 min readUpdated 30 September 2026

Auth.js v5 for B2B Multi-Tenant SaaS in 2026

The tenantId and role callback pattern for Auth.js v5 still works, but the Auth.js team now recommends Better Auth for new projects. Here is what to check before you pick a session strategy.

Auth.js v5 for B2B Multi-Tenant SaaS in 2026

What you'll learn

  • Is Auth.js Still the Right Choice for a New Multi-Tenant SaaS in 2026?
  • How Do You Attach tenantId and Role to an Auth.js v5 Session?
  • Should a B2B Tenant Model Use JWT or Database Sessions?
Article contents

Before you write a single callback for a multi-tenant Auth.js v5 app, settle two questions: is this a new build or an existing NextAuth codebase, and will sessions be JWTs or database rows? The answers change what you build, and the first one changed in the last year. Auth.js has been through a change of stewardship, its v5 line is still tagged beta on npm, and the maintainers now point new projects elsewhere. A GitHub discussion from February 2026 makes the point from the user side: after more than three years of beta releases, one participant argued, the label no longer tells you much.

The multi-tenant pattern itself is older than any library. On Callidus, the multi-tenant clinic SaaS I built solo, the case study documents JWT claims that carry tenantId and role, with data under tenants/{tenantId}/… in Firestore. That stack is React, Firebase and Stripe Connect, not Auth.js. Auth.js applies the same idea on Next.js: put a claim on the session and check it on the server instead of looking the tenant up on every request.

Is Auth.js Still the Right Choice for a New Multi-Tenant SaaS in 2026?

For a new codebase, probably not: the Auth.js team itself recommends Better Auth for new projects, while existing Auth.js and NextAuth apps keep receiving security and urgent fixes. The 26 September 2025 announcement says Auth.js is now maintained by the Better Auth team, that maintenance continues "for security and urgent issues", and that "if you're starting something new" they recommend Better Auth. The Auth.js migration guide now opens with the same news: the project is part of Better Auth.

Then, on 7 July 2026, Vercel announced it had acquired Better Auth. The post cites more than 4.7 million weekly npm downloads, says the library stays MIT licensed and keeps its name, and that founder Bereket Engida and the core team are joining Vercel. This suggests Better Auth is where the ecosystem's investment is going, though the post makes no promise about Auth.js.

For multi-tenancy specifically, Better Auth's organization plugin lists organizations, members, roles, invitations, teams and an active organization on the session. Auth.js has none of these as first-class features. You would build them yourself. One more correction to a common assumption: Better Auth's docs say it also works without a database, through stateless session management, so a database is not a hard requirement.

If you're migrating an existing NextAuth v4 codebase, none of that forces a rewrite this week. This is a greenfield argument, and the rest of this post shows the Auth.js patterns for teams who stay or who inherit a v5 app.

When staying on Auth.js is still reasonable

There are honest reasons to keep it. You may already run v5 in production, and a rewrite of a working auth layer is a project with its own risk and no new customer value. Your team may know the callback model well, or you may need a provider or adapter combination you have already verified. In those cases the practical step is to pin your versions, follow the security fixes, and keep your tenant logic in your own code so that a later move to another library only replaces the session plumbing. That last point is a design choice I'd make regardless: if the tenantId lookup and the enforcement check live in your modules and not inside callbacks, the library becomes a replaceable detail.

How Do You Attach tenantId and Role to an Auth.js v5 Session?

A blank brass key clamped in a small vise mid-engraving, with metal shavings scattered across the worktable

You copy the values from the user onto the token in a jwt() callback, then from the token onto the session in a session() callback. Auth.js's role-based access control guide shows exactly this shape for a role, and a tenantId follows the same path:

callbacks: {
  jwt({ token, user }) {
    if (user) {
      token.role = user.role
      token.tenantId = user.tenantId
    }
    return token
  },
  session({ session, token }) {
    session.user.role = token.role
    session.user.tenantId = token.tenantId
    return session
  },
}

The guide adds the catch that matters for B2B: with this strategy, "if you want to update the role, the user needs to be forced to sign in again." The tenantId line is my adaptation of the documented pattern. You also need to extend the session types, or TypeScript will reject session.user.tenantId.

What v5 changes around the session

Four worn brass toggle switches on a wall panel, one mid-replaced by a sleek new switch with rerouted wiring hanging loose below

The migration guide says one auth() function replaces getServerSession, getToken and the withAuth wrapper, and works in Server Components, route handlers and Edge functions. The next-auth/middleware import is deprecated: you export auth from auth.ts and use it in proxy.ts on Next.js 16, or middleware.ts on older versions. The minimum Next.js version is 14, and environment variables move from the NEXTAUTH_ prefix to AUTH_.

Should a B2B Tenant Model Use JWT or Database Sessions?

Two weathered apartment doors side by side, one with a plain brass keyhole and the other fitted with an intercom buzzer panel

Use database sessions if removing a user must cut access immediately, and JWT sessions only if a delay until sign-in or token expiry is acceptable for your product. Auth.js's session strategy docs describe the trade-off plainly.

JWT sessionsDatabase sessions
StorageToken in an HttpOnly cookieSession ID in a cookie, data server-side
Per-request costNo database lookupA database round trip
RevocationNot possible before expiry without a server-side blocklistModify or delete server-side at any time
Role changeUser must sign in againApplies when you update the row
Edge runtimeFitsMany adapters are not yet Edge-compatible
DefaultUsed unless a database is configuredUsed once you configure an adapter

Consider a hypothetical clinic owner who removes a practitioner from the account. With JWT sessions and no blocklist, that practitioner's token stays valid until it expires, and nothing on your server can reach into it. For per-seat B2B pricing, where admins expect removal to take effect at once, that is usually the deciding row. For a read-heavy internal tool with little churn, the lower per-request cost can win.

The same page says Auth.js mitigates the JWT limitation with shorter expiration times and token rotation, and that database sessions can be modified at any time server-side, which makes features like "sign out everywhere" and concurrent-login limits easier. Shortening the token lifetime narrows the window but does not close it, so decide how long a removed user may stay signed in and write that number into your requirements. It is easier to defend a stated window than an accidental one.

Where Does Tenant Isolation Actually Live?

In your own code: Auth.js gives you a place to put a tenantId claim, and you write the lookup that decides which tenant a signed-in user belongs to and the check that enforces it. The multi-tenant SaaS pattern lives in that lookup and check, whichever library you choose.

Check the session where the data is read

The Auth.js docs on protecting routes are direct: the proxy should not be your only authorization layer, and the session should be verified as close to your data fetching as possible. So check session.user.tenantId inside every server action and route handler that reads tenant data, and treat the proxy as a convenience redirect. A habit that pays off is one helper, for example requireTenant(session), that every handler calls before it touches data, so there is one place to audit and change and not twenty inline checks scattered through the codebase. That is my recommendation, and it matches how the Callidus case study describes its own approach, where access helpers replaced inline role checks so that changes to the access matrix ripple through consistently. When a new route forgets the helper, a code review or a simple test that hits the route as another tenant will catch it.

Split the config when your adapter is not Edge-safe

The migration guide's answer for adapters that don't run on the Edge is two files: auth.config.ts with edge-compatible options, imported by proxy.ts, and auth.ts that adds the adapter for everything else. Do this the moment you add a database adapter.

The Supabase adapter and your own tables

The Supabase adapter page has you create a separate next_auth schema, expose it in the Supabase API settings, and supply a SUPABASE_SERVICE_ROLE_KEY. Its example RLS policy uses next_auth.uid() on Auth.js's own tables. My reading is that your app tables need their own policies against a tenant_id column you populate; the page does not say otherwise, and it does not cover tenants. Keep the service role key on the server only. If you're weighing this against Supabase's built-in auth, the Next.js and Supabase auth pattern covers that route, and running both in one app means two sessions to keep consistent, so pick one.

Organizations and SSO

Adding an SSO provider is configuration, and deciding which organization a new sign-in belongs to is application code. You will need your own organizations and memberships tables and an invite flow, because Auth.js ships none of them. For the wider design, my multi-tenant SaaS architecture guide covers the data side, and the SaaS MVP stack puts auth in context as one decision among several.

A setup order that catches mistakes early

  1. Choose JWT or database sessions first, using the table above, because switching later touches every route that reads the session.
  2. Stand up the adapter against a throwaway database and confirm its schema migrates.
  3. Write the jwt() and session() callbacks, and test with at least two seeded tenants so a scoping bug shows up in a test.
  4. Split auth.config.ts from auth.ts when the proxy enters the picture.
  5. Gate every server action and route handler on session.user.tenantId.
  6. Demote a test user and time how long their access lasts, then decide whether that window fits your pricing tier.

Pick your session strategy this week, and tell me whether the removal scenario in the table changed your answer.

Free resource

Free SaaS MVP Scope Template

A Notion document with the full feature checklist, MVP vs. nice-to-have table, pre-build questions, and cost signals — so you walk into any developer call knowing exactly what to ask for.

Get the template →
DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps using AI-augmented workflows — without agency coordination overhead. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

Does NextAuth support multi tenant SaaS out of the box?

No, Auth.js has no built-in tenant model, so you build tenant isolation on top of its session callbacks. The pattern is the jwt() and session() pair that the role-based access control guide documents, storing a tenantId next to the role. You also write the lookup that decides which tenant a freshly signed-in user belongs to, and the checks in every server action and route handler that enforce it. Nothing in the library stops a user from tenant A being handed tenant B's data if that check is missing, so keep it in one shared helper.

Does Auth.js have an organization feature like Better Auth?

No, Auth.js has no first-class organization or team model. You create your own organizations and memberships tables, an invitation flow, and a lookup from the signed-in user to the tenant they're acting in, then stamp the result on the session through callbacks. Better Auth's organization plugin lists organizations, members, roles, invitations, teams and an active organization on the session as built-in features. That difference matters most for a new B2B project, where those pieces are otherwise weeks of your own work.

Should I use JWT or database sessions for a multi-tenant app?

Use database sessions if removing a user must end their access immediately, and JWT sessions if a delay is acceptable. Auth.js's session strategy docs state that expiring a JWT before its expiry is not possible without a server-side blocklist, while database sessions can be modified server-side at any time. JWTs avoid a database round trip and suit the Edge runtime. For per-seat B2B pricing, where admins expect removal to take effect at once, database sessions are the safer default.

Does the Auth.js Supabase adapter handle Row Level Security for my tenant tables?

No, the adapter covers Auth.js's own tables, and your tenant tables need their own policies. The adapter page has you create a next_auth schema and shows an example policy on Auth.js's user table, but it doesn't describe tenant scoping. Write your policies against a tenant_id column you own and populate, and test them with two seeded tenants. Keep the SUPABASE_SERVICE_ROLE_KEY the adapter requires on the server only, since it is a privileged credential.

Is Auth.js still maintained in 2026?

Yes, but the maintainers describe the work as security and urgent fixes, and they recommend Better Auth for new projects. The September 2025 announcement says Auth.js is now maintained by the Better Auth team and that both v4 and v5 keep being maintained. In July 2026, Vercel announced its acquisition of Better Auth. Treat Auth.js as a fine choice for an existing app you already run, and evaluate Better Auth first for a new build.