I am attempting to implement the nextjs-auth0 SDK on a NextJS application using Next 15. I am familiar with the v3 SDK but am having to use the beta v4 SDK because of the NextJS version on the UI I am building.
The problem I’m running into is this: In v3, auth0 client initialization happened in an api endpoint and hence could receive a request object. This was allowing me to use the same NextJS repo and deployment to serve multiple brand units from separate Auth0 tenants/applications, all through the dynamic detection of the brand via req.headers.host
. Under the v4 SDK, the auth0 client is loaded at app startup rather than during an actual request, preventing the reading of a host header.
I have attempted to delay this client creation until I have access to a request but end up running into issues with middleware.ts. Is there some mechanism by which I can prevent Auth0Client initialization until a host URL can be read without tanking the entire setup?
Okay I have found a functioning workaround for this problem. Rather than storing the Auth0Client initialization in /lib/auth0.ts
, you can move it to the middleware
function in middleware.ts
like so…
import type { NextRequest } from "next/server"
import { Auth0Client } from "@auth0/nextjs-auth0/server";
import { detectBusinessUnitFromRequest } from "@/app/helpers/detectBusinessUnitFromRequest";
let auth0: Auth0Client;
export async function middleware(request: NextRequest) {
const businessUnit = detectBusinessUnitFromRequest(request);
const optionsConfig = {
domain:
process.env[`${businessUnit.toUpperCase()}_AUTH0_DOMAIN`] ||
process.env.AUTH0_DOMAIN,
clientId:
process.env[`${businessUnit.toUpperCase()}_AUTH0_CLIENT_ID`] ||
process.env.AUTH0_CLIENT_ID,
clientSecret:
process.env[`${businessUnit.toUpperCase()}_AUTH0_CLIENT_SECRET`] ||
process.env.AUTH0_CLIENT_SECRET,
appBaseUrl:
process.env[`${businessUnit.toUpperCase()}_APP_BASE_URL`] ||
process.env.APP_BASE_URL,
secret:
process.env[`${businessUnit.toUpperCase()}_AUTH0_SECRET`] ||
process.env.AUTH0_SECRET,
};
auth0 = new Auth0Client(optionsConfig)
return await auth0.middleware(request)
}
// @ts-expect-error - auth0 will be defined at runtime so we don't need this error
export default auth0 as Auth0Client;
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
}
I’m not a big fan of having to use an //@ts-expect-error
here. I would love to hear anyone’s ideas for a more elegant solution.
Okay I’m just keeping this updated in case anyone else runs into the same requirement. Here’s my best solution so far. Omit the lib/auth0.ts
file entirely. Instead, create a helper method such as…
import { Auth0Client } from "@auth0/nextjs-auth0/server";
const createAuth0Client = async (brand: string): Promise<Auth0Client> => {
const optionsConfig = {
domain:
process.env[`${brand.toUpperCase()}_AUTH0_DOMAIN`] ||
process.env.AUTH0_DOMAIN as string,
clientId:
process.env[`${brand.toUpperCase()}_AUTH0_CLIENT_ID`] ||
process.env.AUTH0_CLIENT_ID as string,
clientSecret:
process.env[`${brand.toUpperCase()}_AUTH0_CLIENT_SECRET`] ||
process.env.AUTH0_CLIENT_SECRET as string,
appBaseUrl:
process.env[`${brand.toUpperCase()}_APP_BASE_URL`] ||
process.env.APP_BASE_URL as string,
secret:
process.env[`${brand.toUpperCase()}_AUTH0_SECRET`] ||
process.env.AUTH0_SECRET as string,
};
return new Auth0Client(optionsConfig)
}
export default createAuth0Client;
Then in middleware.ts
:
import type { NextRequest } from "next/server"
import { detectBusinessUnitFromRequest } from "@/app/helpers/detectBusinessUnitFromRequest";
import createAuth0Client from "@/app/helpers/createAuth0Client";
export async function middleware(request: NextRequest) {
const businessUnit = detectBusinessUnitFromRequest(request);
const auth0 = await createAuth0Client(businessUnit);
return await auth0.middleware(request)
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
}
And in a server component:
import { NextRequest } from "next/server";
import { detectBusinessUnitFromRequest } from "@/app/helpers/detectBusinessUnitFromRequest";
import createAuth0Client from "@/app/helpers/createAuth0Client";
import Auth0SessionObj from "@/app/types/Auth0SessionObj";
export const dynamic = 'force-dynamic';
export async function GET(request: NextRequest) {
const brand = detectBusinessUnitFromRequest(request);
const auth0 = await createAuth0Client(brand);
const { user, tokenSet } = await auth0.getSession() as Auth0SessionObj;
// whatever other api logic you want...
}