Aditya Salve
Aditya Salve

Reputation: 25

NextAuth Middleware Failing to Retrieve Token in Production

I'm using NextAuth in a Next.js application with a custom credentials provider. Everything works perfectly on my local development environment, but in production (deployed on Vercel), the middleware fails to retrieve the token, causing users to get stuck on the login page.

Here's my nextauth configuration file:

 import connectMongoDB from "@/libs/mongodb";
    import NextAuth from "next-auth";
    import CredentialsProvider from "next-auth/providers/credentials";
    import User from "@/models/User";
    import bcryptjs from "bcryptjs";
    
    export const authOptions = {
      providers: [
        CredentialsProvider({
          name: "credentials",
          credentials: {
            siteID: { label: "Site ID", type: "text" },
            email: { label: "Email", type: "text" },
            password: { label: "Password", type: "password" },
          },
          async authorize(credentials) {
            const { siteID, email, password } = credentials;
            try {
              await connectMongoDB();
              const user = await User.findOne({ siteID, email });
              if (!user) {
                console.error("No user found with the provided credentials");
                return null;
              }
              const validPassword = await bcryptjs.compare(password, user.password);
              if (!validPassword) {
                console.error("Invalid password");
                return null;
              }
              return user;
            } catch (error) {
              console.error("Error in authorization:", error);
              return null;
            }
          },
        }),
      ],
      callbacks: {
        async jwt({ token, user, session, trigger }) {
          if (trigger === "update" && session?.name) {
            token.name = session.name;
          }
          if (user) {
            return {
              ...token,
              id: user._id,
              name: user.name,
              siteID: user.siteID,
            };
          }
          return token;
        },
        async session({ session, token }) {
          return {
            ...session,
            user: {
              ...session.user,
              id: token.id,
              name: token.name,
              siteID: token.siteID,
            },
          };
        },
      },
      secret: process.env.NEXTAUTH_SECRET || "consultancyproject",
      session: {
        strategy: "jwt",
        maxAge: 15 * 60,
        updateAge: 10 * 60,
      },
      pages: {
        signIn: "/Login",
      },
      events: {
        async signOut(message) {
          console.log("User signed out", message);
        },
        async sessionExpired(message) {
          console.log("Session expired", message);
        },
      },
    };
    
    const handler = NextAuth(authOptions);
    
    export { handler as GET, handler as POST };

Here is my Middleware file:

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

export async function middleware(request) {
  console.log("Middleware called. Request URL:", request.url);
  console.log("NEXTAUTH_SECRET:", process.env.NEXTAUTH_SECRET);
  
  let token;
  try {
    token = await getToken({
      req: request,
      secret: process.env.NEXTAUTH_SECRET,
    });
    console.log("Token retrieved:", token);
  } catch (error) {
    console.error("Error retrieving token:", error);
    return NextResponse.redirect(new URL("/Login", request.url));
  }

  const path = request.nextUrl.pathname;
  console.log("Requested path:", path);

  const publicPaths = [
    "/",
    "/Signup",
    "/Login",
    "/ForgotPassword",
    "/ResetPassword",
    "/ChoosePlan",
  ];

  const isPublicPath = publicPaths.includes(path);
  console.log("Is public path:", isPublicPath);

  // Redirect authenticated users away from public paths to the dashboard
  if (isPublicPath && token) {
    console.log("Authenticated user trying to access public path. Redirecting to /Dashboard");
    return NextResponse.redirect(new URL("/Dashboard", request.url));
  }

  // Redirect unauthenticated users trying to access protected paths to the login page
  if (!isPublicPath && !token) {
    console.log("Unauthenticated user trying to access protected path. Redirecting to /Login");
    return NextResponse.redirect(new URL("/Login", request.url));
  }

  // Continue with the request if the user is authenticated or accessing a public path
  console.log("User is authenticated or accessing public path. Continuing with request.");
  return NextResponse.next();
}

export const config = {
  matcher: [
    "/",  
    "/Admin",
    "/Cases",
    "/Dashboard",
    "/ForgotPassword",
    "/Home",
    "/NotAuthorized",
    "/PaymentSuccess",
    "/Reports",
    "/Signup",
    "/ChoosePlan",
    "/Emails",
    "/Form",
    "/ResetPassword",
    

], };

In production, the logs indicate that the token is not being retrieved:

***User is authenticated or accessing public path. Continuing with request.
GET /Login
Is public path: true
GET /Login
Requested path: /Login
Token retrieved: null***

These are the logs from nextauth file:

Session callback token: {
  email: '[email protected]',
  sub: '668e24f188a5984c9650b971',
  id: '668e24f188a5984c9650b971',
  siteID: 'GC7U35TAFX',
  iat: 1721071538,
  exp: 1723663538,
  jti: '63301835-155f-4ef0-8eb9-7cff5f93d203'
}
JWT callback token: {
  email: '[email protected]',
  sub: '668e24f188a5984c9650b971',
  id: '668e24f188a5984c9650b971',
  siteID: 'GC7U35TAFX',
  iat: 1721071538,
  exp: 1723663538,
  jti: '63301835-155f-4ef0-8eb9-7cff5f93d203'
}

What I've checked so far:

  1. Verified that NEXTAUTH_SECRET is correctly set in Vercel's environment variables.
  2. Ensured that secure cookies are enabled in production.
  3. Added detailed logging to track the token retrieval process.

Questions:

  1. Why is the token retrieval failing in production while it works perfectly in the development environment?
  2. How can I further debug and fix this issue to ensure token retrieval works in production? Any insights or suggestions would be greatly appreciated!

Upvotes: 0

Views: 147

Answers (2)

AndriyFM
AndriyFM

Reputation: 1603

you can try this implementation

import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';

// Retrieve token from the request
const secureSessionToken = cookies().get('__Secure-authjs.session-token')?.name;
const unsecureSessionToken = cookies().get('authjs.session-token')?.name;
const secret = process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET ?? '';
const salt = secureSessionToken ?? unsecureSessionToken ?? '';
const isVercelProd = process.env.VERCEL_ENV === 'production';
const cookieName = !isVercelProd ? unsecureSessionToken : secureSessionToken;
const token = await getToken({ req, secret, salt, cookieName, raw: true });

Upvotes: 0

Ahmet Firat Keler
Ahmet Firat Keler

Reputation: 4055

Don't forget to add environment variables in your Dockerfile as well (if you use any)

E.x.

//...

ENV API_BASE_URL=https://api.example.com
ENV API_KEY=i175e99e-99x1-5e18-52bb-1u98c30ef83c
ENV AUTH_SECRET=an2af0596604344x3g015a789ba447uj
ENV AUTH_URL=https://www.example.com/api/auth

//...

Upvotes: 0

Related Questions