Search code examples
authenticationnext.jsmiddlewaremulti-tenantclerk

NextJs multi-tenant middleware with Clerk Auth Middleware


See my exact error screenshots at the end.

Context:

I developed a NextJs Web App with Clerk Auth and It worked fine! I later decided to add Multitenancy by using Next Multitenancy Template which used a middleware to look for domain name changes, also the template used Next Auth. So, I merged my previous Web App with it. I left next auth for the multitenancy admin system and want to use clerk auth for the tenants and so far, there is no conflict there.

Now, my clerk auth middleware and multitenancy middleware are at a conflict.

Previous App Directory

enter image description here

App Directory after merging

enter image description here

I copied all my app files from my original app inside the [domain] directory of the multitenant template and resolved all dependency issues, components, hooks, libs to the best of my knowledge.

My middleware code :


import { NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";


// // Developer Message 2 : This is for Clerk support. To Further Check Proceed To Link : https://clerk.com/docs/quickstarts/nextjs
// // After this (auth) folder was created in app
// //Below is Developer Message 3

import { authMiddleware } from "@clerk/nextjs";
 
// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your middleware

export default authMiddleware({

      // Developer Message 3
      //Public Routes Enable Unauthorised users to visit below mentioned routes 

      publicRoutes: ["/","/api/webhook"]

});



export const config = {
  matcher: [
    /*
     * Match all paths except for:
     * 1. /api routes
     * 2. /_next (Next.js internals)
     * 3. /_static (inside /public)
     * 4. all root files inside /public (e.g. /favicon.ico)
     */
    '/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)',  // from clerk auth
    "/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)", //from multitenancy template
  ],
};

export async function middleware(req: NextRequest) {
  const url = req.nextUrl;

  // Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3000)
  const hostname = req.headers
    .get("host")!
    .replace(".localhost:3000", `.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`);

  const searchParams = req.nextUrl.searchParams.toString();
  // Get the pathname of the request (e.g. /, /about, /blog/first-post)
  const path = `${url.pathname}${
    searchParams.length > 0 ? `?${searchParams}` : ""
  }`;

  // rewrites for app pages
  if (hostname == `app.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) {
    const session = await getToken({ req });
    if (!session && path !== "/login") {
      return NextResponse.redirect(new URL("/login", req.url));
    } else if (session && path == "/login") {
      return NextResponse.redirect(new URL("/", req.url));
    }
    return NextResponse.rewrite(
      new URL(`/app${path === "/" ? "" : path}`, req.url),
    );
  }

  // special case for `vercel.pub` domain
  if (hostname === "vercel.pub") {
    return NextResponse.redirect(
      "https://vercel.com/blog/platforms-starter-kit",
    );
  }

  // rewrite root application to `/home` folder
  if (
    hostname === "localhost:3000" ||
    hostname === process.env.NEXT_PUBLIC_ROOT_DOMAIN
  ) {
    return NextResponse.rewrite(
      new URL(`/home${path === "/" ? "" : path}`, req.url),
    );
  }

  // rewrite everything else to `/[domain]/[slug] dynamic route
  return NextResponse.rewrite(new URL(`/${hostname}${path}`, req.url));
}

I've placed both middleware code in the same but two defaults cannot exist together and so I tried to export the multitenancy middleware() without the default keyword and it seemingly worked. I also combined both configs.

I'm added tags enveloping the html tags so that authentication work on the whole app. I also experimented with placing tags inside the [domain] directory app layout to only make it work for tenants, but the error did not go away.

Whenever I sign in through the clerk auth and I am supposed to land on dashboard then the following error occurs

enter image description here

enter image description here

I think my middleware is causing the issue and I'm out of ideas.


Solution

  • I did something like this, and its working

    import { authMiddleware, redirectToSignIn } from "@clerk/nextjs";
    import { NextResponse } from "next/server";
    
    // This example protects all routes including api/trpc routes
    // Please edit this to allow other routes to be public as needed.
    // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware
    export default authMiddleware({
      publicRoutes: ["/"],
      afterAuth: (auth, req) => {
        const url = req.nextUrl;
    
        // Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3123)
        // biome-ignore lint/style/noNonNullAssertion: <explanation>
        let hostname = req.headers.get("host")!.replace(".localhost:3123", `.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`);
    
        // special case for Vercel preview deployment URLs
        if (hostname.includes("---") && hostname.endsWith(`.${process.env.NEXT_PUBLIC_VERCEL_DEPLOYMENT_SUFFIX}`)) {
          hostname = `${hostname.split("---")[0]}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`;
        }
    
        const searchParams = req.nextUrl.searchParams.toString();
        // Get the pathname of the request (e.g. /, /about, /blog/first-post)
        const path = `${url.pathname}${searchParams.length > 0 ? `?${searchParams}` : ""}`;
    
        // rewrites for app pages
        if (hostname === `app.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) {
          if (!auth.userId && !auth.isPublicRoute) {
            const prefix = process.env.NODE_ENV === "development" ? "http://" : "https://";
            return redirectToSignIn({ returnBackUrl: `${prefix}${hostname}/` });
          }
    
          return NextResponse.rewrite(new URL(`/app${path === "/" ? "" : path}`, req.url));
        }
    
        // special case for `vercel.pub` domain
        //   if (hostname === 'vercel.pub') {
        //     return NextResponse.redirect(
        //       'https://vercel.com/blog/platforms-starter-kit',
        //     );
        //   }
    
        // rewrite root application to `/home` folder
        if (hostname === "localhost:3123" || hostname === process.env.NEXT_PUBLIC_ROOT_DOMAIN) {
          return NextResponse.rewrite(new URL(`/home${path === "/" ? "" : path}`, req.url));
        }
        // console.log("here");
    
        // rewrite everything else to `/[domain]/[slug] dynamic route
        return NextResponse.rewrite(new URL(`/${hostname}${path}`, req.url));
      },
    });
    
    export const config = {
      matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
    };