Next.js Discord

Discord Forum

Best Practice for Route Protection and Redirection in Next.js with Admin and User Dashboards

Answered
Briard posted this in #help-forum
Open in Discord
BriardOP
I have an admin dashboard, a user dashboard, a project dashboard (for admin and users) and a login page in my Next.js app. Should I handle route protection (authorization) and redirects in middleware, layout.tsx, or each individual page?

this is my middleware approach and it's working fine
export async function middleware(request: NextRequest) {
  const path = request.nextUrl.pathname;

  const { data: session } = await betterFetch<Session>(
    "/api/auth/get-session",
    {
      baseURL: request.nextUrl.origin,
      headers: {
        cookie: request.headers.get("cookie") || "", // Forward the cookies from the request
      },
    }
  );

  const isOnAdminPage = path.startsWith("/admin");
  const isOnDashboardPage = path.startsWith("/dashboard");
  const isOnSignInPage = path.startsWith("/sign-in");
  const isAdmin = session?.user?.role === "admin";

  // If no session, redirect to sign-in
  if (!session && !isOnSignInPage) {
    return NextResponse.redirect(new URL("/sign-in", request.url));
  }
  if (session) {
    //   If authenticated and on sign-in page, redirect based on role
    if (isOnSignInPage) {
      const redirectTo = isAdmin ? "/admin" : "/dashboard";
      return NextResponse.redirect(new URL(redirectTo, request.url));
    }
    // If non-admin tries to access /admin, redirect to /dashboard
    if (isOnAdminPage && !isAdmin) {
      return NextResponse.redirect(new URL("/dashboard", request.url));
    }
    // If admin tries to access /dashboard, redirect to /admin
    if (isOnDashboardPage && isAdmin) {
    
      return NextResponse.redirect(new URL("/admin", request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/admin/:path*", "/sign-in", "/projects/:path*", "/dashboard"], 
};
Answered by luis_llanes
I'm glad!
And yeah... don't make your layout a client component the whole point of a layout is that's (mostly) static therefore it's better if it's a server component, plus you can make the auth check in there and test if it works for all the use cases.

If the layout check is enough then you won't need the per page check.
Careful tho, if the layout persists and don't re-render because it'll be a Server Component this means the auth check will only been done once, the first time. If your session expires or lets say, you change accounts from within the layout you might have stale data. Andddd important this will turn your whole layout and all pages inside of it dynamic (if they weren't already) since it's accessing headers.

I believe this is why people recommend doing auth checks at the page/route level
View full answer

27 Replies

BriardOP
I’m using better-auth and have already implemented the middleware approach for this. How can I ensure that the routes are properly protected and users are redirected accordingly? Should the logic be placed in the middleware, the layout, or per page?
BriardOP
bumb
Yacare Caiman
Here explains how to implement the middleware is very simple

https://www.better-auth.com/docs/integrations/next#middleware
@Yacare Caiman Here explains how to implement the middleware is very simple https://www.better-auth.com/docs/integrations/next#middleware
BriardOP
i integrated it with middleware already as i shared in the code, but in previous thread someone adviced me not to do that
Yacare Caiman
I recommend you that if a user have to acces a page that doesn’t is allowed to use, show them q 404 page
@Yacare Caiman I recommend you that if a user have to acces a page that doesn’t is allowed to use, show them q 404 page
BriardOP
yes but im asking is the middleware approach fine ? because in previous post i was recommended not to do authorization and heavy fetching in middleware
@Yacare Caiman You can get this as reference https://github.com/better-auth/better-auth/blob/main/demo/nextjs/middleware.ts
BriardOP
i see in the middleware only checking the existance of cookie but how about the admin page? what if a user tried to access the admin dashboard
Yacare Caiman
That's easy to handle
@Yacare Caiman Check this
BriardOP
i checked it they didnt handle it
Is very simple to make a middleware
I don’t think nobody answering (so far) actually cares to understand what you’re actually talking about lol

— He said he’s got his middleware set up and it’s working fine as it is now.
Middleware checks and redirects are good for a first check.

I’ve also had this concern before and I heard people recommending implementing more validations even if you already have a middleware set up (and properly working, even Next.ja recommends it lol) .

I would suggest you add another deeper check on the layout level for your /admin/ section
And have in mind all server actions and route handlers need to check for users before performing any operation, after all they’re public endpoints.. so are your pages
So if we want to follow that pattern of protection at the individual page level would be easier to abstract that logic to a function and call it at the top of every page.

If the user somehow bypassed the previous validations this last check won’t allows the page to render and it’ll redirect/show the error fallback instead. Hope that was useful lol
@luis_llanes I don’t think nobody answering (so far) actually cares to understand what you’re actually talking about lol — He said he’s got his middleware set up and it’s working fine as it is now.
BriardOP
thanks for answering,

export async function checkUserRoleAdmin(redirectPath: string) {
  const session = await auth.api.getSession({ headers: await headers() });
  if (session?.user?.role !== "admin") {
    redirect(redirectPath);
  }
}


i implemented this function and called it in every page inside my admin pages , but what if i am inside a client side rendered page?
Client side rendered pages such as?

Being a client component or fully client side page? How are you implementing that?
Ultimately if you’re routing through Next.js every reachable url segment has to match a page.tsx (route.ts for API) inside the App folder

And in this case your entry point will be your page.tsx, which i recommend should be kept a server component (instead of turning it into a client component by putting “use client” ) , if you need the rest to be a client component make a client component separately even if all your page.tsx is doing is:
- calling the session function
- returning the actual page that’s the fully clientside component
You’re welcome, I have to ask, was it somewhat helpful? 😅
@luis_llanes You’re welcome, I have to ask, was it somewhat helpful? 😅
BriardOP
yes it was , i will start making everything server component, i had my layout as client components
now it's server component and in each page (server component) i added

await checkUserRoleAdmin("/auth/sign-in");


which is basically a function that redirects the user to his own place
export async function checkUserRoleAdmin(redirectPath: string) {
  const session = await auth.api.getSession({ headers: await headers() });
  console.log("this is for admin yani");

  if (session?.user?.role !== "admin") {
    redirect(redirectPath);
  }
}
@Briard yes it was , i will start making everything server component, i had my layout as client components
I'm glad!
And yeah... don't make your layout a client component the whole point of a layout is that's (mostly) static therefore it's better if it's a server component, plus you can make the auth check in there and test if it works for all the use cases.

If the layout check is enough then you won't need the per page check.
Careful tho, if the layout persists and don't re-render because it'll be a Server Component this means the auth check will only been done once, the first time. If your session expires or lets say, you change accounts from within the layout you might have stale data. Andddd important this will turn your whole layout and all pages inside of it dynamic (if they weren't already) since it's accessing headers.

I believe this is why people recommend doing auth checks at the page/route level
Answer
BriardOP
here !