reactjstypescriptnext.js

Middleware Redirection Not Working Properly


I’m working on a Next.js project and I’ve implemented a middleware to protect certain routes and redirect users based on their session cookie. However, I’m experiencing an issue where the redirection isn't working as expected, specifically for the /account route. When the user accesses /account without a valid session cookie, I want them to be redirected to the login page. But instead, it seems like the request goes through and an API call is made, even though the session cookie is missing.

Middleware code:

"use server";
import { NextRequest, NextResponse } from "next/server";
import { decrypt } from "@lib/session"; // Import decrypt for session validation
import { cookies } from "next/headers";

// Define protected and public routes
const protectedRoutes = ["/account"];
const publicRoutes = ["/login", "/signup"];

export default async function middleware(req: NextRequest) {
  const path = req.nextUrl.pathname;
  const isProtectedRoute = protectedRoutes.includes(path);
  const isPublicRoute = publicRoutes.includes(path);

  // Get the session cookie
  const cookie = (await cookies()).get("session")?.value;

  // If no session cookie and trying to access a protected route, redirect to login
   if (isProtectedRoute && !cookie) {
    return NextResponse.redirect(new URL("/login", req.nextUrl));
  }

  // If the route is public and the user has an active session, redirect to home
  const session = cookie ? await decrypt(cookie) : null;
  if (isPublicRoute && session?.userToken) {
    return NextResponse.redirect(new URL("/", req.nextUrl));
  }

  // Continue with the request if everything is fine
  return NextResponse.next();
}

// Middleware configuration
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"], // Routes to apply the middleware to
};

Expected Behavior:

Problem: When I navigate to /account without a session cookie, instead of being redirected to /login, an API request is made. The error message I get is:

GET /account 500 in 261ms
⨯ Error [AxiosError]: Request failed with status code 401

Things I've Tried:

Project structure: Project structure image

Axios config:

import Axios from "axios";

const axios = Axios.create({
  baseURL: "http://localhost:8000/api/app",
  headers: {
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest",
  },
  withCredentials: true,
  withXSRFToken: true,
});

export default axios;

Account page:

import AccountDetails from "@/components/account/AccountDetails";
import AccountSkeleton from "@components/skeletons/AccountSkeleton";
import { fetchUserData } from "@/actions/user";

export default async function Account() {
  const userData = await fetchUserData();

  if (!userData) {
    return <AccountSkeleton />;
  }

  return <AccountDetails userData={userData} />;
}

User data function:

const fetchUserData = async () => {
  const session = await getSession();
  if (!session?.userToken) {
    throw new Error("User not authenticated");
  }
  const response = await axios.get("/user/account", {
    headers: {
      Authorization: `Bearer ${session?.userToken}`,
    },
  });
  return response.data;
};

I do all of this and still get the following error: Unhandled error

Error: User not authenticated
at fetchUserData (user.ts:10:11)
at async Account (page.tsx:6:20)

NextJS config

next.config.mjs:

import path from "path";

import { fileURLToPath } from "url";
import { dirname } from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    config.resolve.alias["@"] = path.resolve(__dirname, "src");
    return config;
  },
};

export default nextConfig; 

Solution

  • I found a provisional fix. I know it's not the correct way to do it, but I'm learning.

    In the main /account page, check if the user is authenticated:

    import AccountDetails from "@/components/account/AccountDetails";
    import AccountSkeleton from "@components/skeletons/AccountSkeleton";
    import { fetchUserData } from "@/actions/user";
    import { isAuthenticated } from "@/actions/auth";
    import { redirect } from "next/navigation";
    
    export default async function Account() {
      const authCheck = await isAuthenticated();
      if (!authCheck) {
        redirect("/login");
      } else {
        const userData = await fetchUserData();
    
        if (!userData) {
          return <AccountSkeleton />;
        }
    
        return <AccountDetails userData={userData} />;
      }
    }