Search code examples
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:

  • When the user tries to access /account without a valid session cookie, the middleware should redirect them to /login.
  • When the user has an active session, the middleware should allow access to /account and prevent redirects.

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:

  • Checked Middleware Execution: Added console.log statements in the middleware to ensure that it’s being executed correctly. The logs confirm that the redirection logic is reached, but the API call is still being made before the redirection occurs.
  • Confirmed Cookie Handling: I’ve verified that the session cookie is properly set and retrieved using cookies(). When the cookie is not present, the middleware should handle the redirection, but this isn’t happening as expected.
  • Verified Redirect Logic: The NextResponse.redirect() is being called properly when there’s no session cookie, but the API is still triggered before the redirect is processed.
  • Check Frontend Logic: I’ve also checked the frontend to ensure there aren’t any unexpected API calls being made in the React component before the page renders. However, it appears that the request is made on the server side before the middleware can apply the redirect.

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} />;
      }
    }