-
Notifications
You must be signed in to change notification settings - Fork 429
Description
Checklist
- I have looked into the Readme, Examples, and FAQ and have not found a suitable solution or answer.
- I have looked into the API documentation and have not found a suitable solution or answer.
- I have searched the issues and have not found a suitable solution or answer.
- I have searched the Auth0 Community forums and have not found a suitable solution or answer.
- I agree to the terms within the Auth0 Code of Conduct.
Describe the problem you'd like to have solved
I'm building a multi-tenant NextJS 15 app with url access as such: client1.myapp.com, client2.myapp.com, etc. I have a version of this app running in NextJS 14 using the 3.5.0 auth0/nextjs-auth0 package. I'm struggling a bit to get the 4.0 auth0-nextjs sdk working.
I've hacked the following together which seems to work, but before I proceed much further, I'd welcome input on whether what I'm doing is off-base. I'm concerned with moving away from the singleton approach used in the examples to instead creating a new Auth0 client instance for each request since the Auth0 config needs to change based on the hostname/subdomain.
Feedback very much appreciated.
// lib/get-auth0-client.ts
import { Auth0Client } from "@auth0/nextjs-auth0/server";
import { getTenantFromHost } from "./tenant-utils";
export function getAuth0ClientForHost(host: string) {
const tenant = getTenantFromHost(host);
// Determine protocol based on environment
const isProduction = process.env.NODE_ENV === 'production';
const protocol = isProduction ? 'https' : 'http';
return new Auth0Client({
domain: process.env.AUTH0_DOMAIN!,
clientId: process.env.AUTH0_CLIENT_ID!,
clientSecret: process.env.AUTH0_CLIENT_SECRET!,
secret: process.env.AUTH0_SECRET!,
appBaseUrl: `${protocol}://${host}`,
authorizationParameters: tenant?.orgId ? {
organization: tenant.orgId
} : undefined
});
}
// lib/tenant-utils.ts
export type Tenant = {
name: string;
orgId: string;
}
// static here for POC; will eventually move to postgres
const tenants: Record<string, Tenant> = {
'client1': {
name: 'Client 1',
orgId: process.env.AUTH0_CLIENT1_ORG_ID!,
},
'client2': {
name: 'Client 2',
orgId: process.env.AUTH0_CLIENT2_ORG_ID!,
},
};
export function getTenantFromHost(host: string): Tenant | null {
const subdomain = host.split('.')[0];
return tenants[subdomain] || null;
}
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { getAuth0ClientForHost } from "@/lib/get-auth0-client";
export async function middleware(request: NextRequest) {
const host = request.headers.get('host') || '';
// Get the tenant-specific Auth0 client
const auth0 = getAuth0ClientForHost(host);
// Process middleware
const authRes = await auth0.middleware(request);
if (request.nextUrl.pathname.startsWith('/auth')) {
return authRes;
}
// For protected routes, check if user is authenticated
if (request.nextUrl.pathname.startsWith('/dashboard') ||
request.nextUrl.pathname.startsWith('/profile')) {
const session = await auth0.getSession(request);
if (!session) {
const loginUrl = new URL('/auth/login', request.url);
loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
}
// Return the auth response to ensure cookies are handled
return authRes;
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|_next/media|favicon.ico|sitemap.xml|robots.txt).*)",
],
};
// app/page.tsx
import { headers } from "next/headers";
import { getTenantFromHost } from "@/lib/tenant-utils";
import { getAuth0ClientForHost } from "@/lib/get-auth0-client";
export default async function Home() {
const headersList = await headers();
const host = headersList.get("host") || "";
const tenant = getTenantFromHost(host);
const auth0 = getAuth0ClientForHost(host);
const session = await auth0.getSession();
return (
<main>
<h1>Welcome to {tenant?.name || "Default"}</h1>
{session ? (
<>
<p>Logged in as: {session.user.name}</p>
<p>Organization: {session.user.org_id}</p>
<a href="/auth/logout">Logout</a>
</>
) : (
<>
<a href="/auth/login">Login</a>
</>
)}
<div>
<p>Clients:</p>
<ul>
<li><a href="http://client1.localtest.me:3000">client1.localtest.me:3000</a></li>
<li><a href="http://client2.localtest.me:3000">client2.localtest.me:3000</a></li>
</ul>
</div>
</main>
);
}
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// resolve issue with WebSocket connection to subdomains causing webpack-hmr failure
allowedDevOrigins: ['localtest.me', '*.localtest.me'],
};
export default nextConfig;
With this logic in place, within the Auth0 dashboard the following configs seem to work exactly as expected:
- Allowed Callback URLs:
http://{organization_name}.localtest.me:3000/auth/callback
- Allowed Logout URLs:
http://*.localtest.me:3000
So, as I say, this works as desired, but I'd really like some guidance if I'm off-the-mark with regard to this multi-tenant architecture for Auth0 v4.
Describe the ideal solution
Since this is a feature request post, the request would be for a reference architecture for multi-tenant subdomain documentation.
Alternatives and current workarounds
No response
Additional context
No response