❗️Issue: No support for dynamic multi-tenant (subdomain-based) configuration with organizations in @auth0/nextjs-auth0

We’re building a multi-tenant SaaS application where each tenant lives on a different subdomain:

alpha.mysite.com → org_id: org_alpha  
beta.mysite.com  → org_id: org_beta  

With the new @auth0/nextjs-auth0 (v4+), the SDK uses a static auth() singleton and reads config either from env vars or via authConfig during initialization. This makes it impossible to dynamically configure Auth0 per request, which is critical for subdomain-based multi-tenancy with Auth0 Organizations.

What we need to do

  • Dynamically determine the organization based on the request hostname.
  • Support multiple baseURLs and organization IDs depending on the request.
  • Make sure the session and login logic reflect the correct org for each tenant.

What we tried

We can pass the organization in handleLogin():

import { handleLogin } from '@auth0/nextjs-auth0';

export default async function login(req, res) {
  const orgId = extractOrgIdFromHost(req.headers.host);
  return await handleLogin(req, res, {
    authorizationParams: {
      organization: orgId,
    },
  });
}

Why this is a problem

The singleton approach assumes a single set of values for the entire application. But in a multi-tenant app where tenants are routed via subdomains (and often tied to different organizations in Auth0), this doesn’t work:

  • We can’t authenticate per-tenant securely.
  • We can’t keep sessions scoped to their organization.
  • We can’t use the same app for multiple orgs dynamically.

can you help me understand what i’m getting wrong from the documentation to implement this feature? this is making me go crazy :smiley:

thx

4 Likes

In the meantime, I’ve found a working solution — though far from ideal.

Instead of relying on the static auth() singleton or sharing a single Auth0Client instance across the app, I now create a new instance dynamically using a small helper function:

export async function createAuth0Client({ hostname }: CreateAuth0ClientProps): Promise<Auth0Client> {
  const orgName = getOrgNameFromHost(hostname);
  const orgData = await getOrganizationByHost(orgName);

  const auth0 = new Auth0Client({
    authorizationParameters: {
      scope: process.env.AUTH0_SCOPE,
      audience: process.env.AUTH0_AUDIENCE,
      organization: orgData?.organization.id,
    },
    appBaseUrl: `https://${hostname}`,
  });

  return auth0;
}

I call this function wherever I need access to Auth0 — in middleware, server components, and even in client components when necessary.

While this approach seems to work fine across all contexts, it’s clearly not efficient. A new Auth0Client is instantiated for almost every request, which feels unnecessary — especially considering that most of this information (like the organization) could conceptually be derived from the user’s session or some persistent context.

Ideally, it would be possible to share or memoize client instances per tenant or per request context, or even better: the SDK would allow for a request-scoped configuration where the client is agnostic and just picks up the right parameters from the environment or session.

Still, for now, this workaround allows multi-organization support via subdomains to work as expected — even if it comes with overhead.

Building a Multi-Tenant SaaS with Auth0 and Subdomain Routing (And Why It’s Not That Simple)

You’re absolutely right to be frustrated — the limitations you’re encountering stem from how the new @auth0/nextjs-auth0 SDK (v4+) is architected. At its core, the SDK assumes a single static configuration via auth(), either through environment variables or a one-time authConfig during app startup. This design works great for single-tenant apps, but not when you’re dealing with dynamic tenants split across subdomains — like alpha.mysite.com, beta.mysite.com, etc.

Here’s a breakdown of what’s going wrong and why:

:white_check_mark: What You Got Right:

  • You’re correctly extracting the tenant (organization) ID from the request’s host header.
  • You’re correctly passing the organization param into handleLogin().

:cross_mark: What’s Blocking You:

  • The SDK singleton pattern means you can’t dynamically change base URLs, client IDs, or secrets per request.
  • Session cookies and login state are shared across tenants, which is insecure and non-isolated.
  • Even though handleLogin allows you to pass the organization, the underlying SDK config (used for things like session handling and redirects) still assumes a single Auth0 app setup.

:wrench: Potential Workarounds:

Here are a few possible directions to resolve this:

  1. Middleware + Proxy Pattern: Put a reverse proxy (like NGINX or Next.js middleware) in front of your app to route requests to entirely different server instances (or Next.js apps) per tenant, each with its own environment config.
  2. Dynamic Server Initialization: While @auth0/nextjs-auth0 v4 doesn’t support dynamic config, you could fork or wrap parts of the SDK to lazy-load tenant-specific config per request (though this is not trivial and could break on updates).
  3. Consider Other Auth0 SDKs: If possible, drop down to using passport, or the lower-level auth0 Node SDK, where you have full control over Auth0 config per request.
  4. Wait for or Request SDK Support: The community has asked for dynamic multi-tenant support for a while. Consider upvoting or filing an issue on GitHub if you haven’t already.

Multi-tenancy at the subdomain level requires dynamic auth logic, and the current Auth0 SDK makes this extremely difficult. You’re not misreading the docs — they just weren’t built for your use case.

If you’re also looking to streamline your online tools, you can check out y2mate.now, a reliable YouTube video downloader that makes grabbing your favorite videos quick and easy.