Search code examples
authenticationjwtmiddlewarenext.js13

Having a jwt must be providecd error in NextJs 13


The Error Iam getting is : data: { error: 'jwt must be provided' }

When User logIn by providing the correct email and password then the user is redirected to "/" page the logic is written in middleware.ts you can see in this code.But using react query is showing error why like normal axios.get will work but this doesnot work. I need tanstack query in my project so tried to use it in this UserDetails.jsx to show the user information that is stored in login/route.ts

UserDetails.tsx (This code doesnot work):
    "use client";
    
    interface Data {
      id: string;
      username: string;
      email: string;
    }
    
    export default function UserDetails() {
      const { data } = useQuery({
        queryFn: async () => {
          const { data } = await axios.get("http://localhost:3000/api/me");
          return data as Data;
        },
      });
    
      return (
        <div>
          <h1>{JSON.stringify(data)}</h1>
        </div>
      );
    }

This is login api that takes email and password and validates and after validating it generates jwt token and stores it in httponly cookie

 export async function POST(request: NextRequest) {
      try {
        const body = await request.json();
    
        const schema = z.object({
          email: z.string().email({ message: "Invalid Email !!" }),
          password: z.string(),
        });
    
        if (body.email === "" && body.password === "") {
          return NextResponse.json(
            { error: "Please fill all the fields !!" },
            { status: 400 }
          );
        }
    
        if (body.email === "") {
          return NextResponse.json(
            { error: "Email is required !!" },
            { status: 400 }
          );
        }
    
        if (body.password === "") {
          return NextResponse.json(
            { error: "Password is required !!" },
            { status: 400 }
          );
        }
    
        const validatedData = schema.parse(body);
        const { email, password } = validatedData;
    
        const user = await FindByEmail(email);
    
        if (!user) {
          return NextResponse.json(
            { error: "User doesn't Exists !!" },
            { status: 400 }
          );
        }
    
        const passwordsMatched = await user.matchPasswords(password);
        if (!passwordsMatched) {
          return NextResponse.json(
            { error: "Password is incorrect !!" },
            { status: 400 }
          );
        }
    
        const tokenData = {
          id: user._id,
          username: user.username,
          email: user.email,
        };
    
        const token = await jwt.sign(tokenData, process.env.JWT_SECRET!, {
          expiresIn: "1d",
        });
    
        const response = NextResponse.json(
          {
            message: "Login Successful",
          },
          { status: 200, statusText: "set cookie" }
        );
    
        response.cookies.set("jwt", token, {
          secure: true,
          httpOnly: process.env.NODE_ENV !== "development",
          sameSite: "strict",
          maxAge: 1 * 24 * 60 * 60 * 1000,
        });
    
        return response;
      } catch (error) {
        if (error instanceof z.ZodError) {
          return NextResponse.json({ error: error.flatten() }, { status: 400 });
        } else {
          return NextResponse.json(
            {
              error: "Something went wrong !!" + error,
            },
            { status: 400 }
          );
        }
      }
    }

This api is used to get the userInformation

api/me/route.ts :

    
    connect();
    
    export async function GET(request: NextRequest) {
      try {
        const userId = await getDataFromToken(request);
        const user = await User.findOne({ _id: userId }).select("-password");
        return NextResponse.json({
          message: "User Found",
          data: user,
        });
      } catch (error: any) {
        return NextResponse.json({ error: error.message }, { status: 400 });
      }
    }

This is used to get the id from jwt token as id is stored in jwt along with username and email

getDataFromToken.ts : 
    import { NextRequest } from "next/server";
    import jwt from "jsonwebtoken";
    
    export const getDataFromToken = (request: NextRequest) => {
      try {
        const token = request.cookies.get("jwt")?.value || "";
        const decodedToken: any = jwt.verify(token, process.env.JWT_SECRET!);
        return decodedToken.id;
      } catch (error: any) {
        throw new Error(error.message);
      }
    };

This is the middleware.ts provided by NextJs which checks the cookies have jwt token or not and if that token is valid then redirects to "/" page otherwise not

middleware.ts:
    import { verifyJwtToken } from "@/libs/verifyToken";
    import { NextResponse } from "next/server";
    import { NextRequest } from "next/server";
    
    export async function middleware(request: NextRequest) {
      const token = request.cookies.get("jwt")?.value;
      const path = request.nextUrl.pathname;
      const PublicPath = path === "/login" || path === "/register";
    
      try {
        const verifiedToken =
          token &&
          (await verifyJwtToken(token).catch((e) => {
            throw new Error(e);
          }));
    
        if (PublicPath && token && verifiedToken) {
          return NextResponse.redirect(new URL("/", request.nextUrl));
        }
    
        if (!PublicPath && (!token || !verifiedToken)) {
          return NextResponse.redirect(new URL("/login", request.nextUrl));
        }
      } catch (error) {
        return NextResponse.redirect(new URL("/login", request.nextUrl));
      }
    }
    
    export const config = {
      matcher: ["/login", "/register", "/"],
    };

This code is used to verify the jwt token if it is valid or not using jose and if jwt is valid then send the payload associated with it.

verifyToken.ts:
    import { jwtVerify } from "jose";
    
    export const getJwtSecretKey = () => {
      const secret = process.env.JWT_SECRET!;
      if (!secret || secret.length === 0) {
        throw new Error("The environment variable JWT_SECRET is not set.");
      }
      return secret;
    };
    
    export async function verifyJwtToken(token: string) {
      try {
        if (!token) {
          return;
        }
        const verified = await jwtVerify(
          token,
          new TextEncoder().encode(getJwtSecretKey())
        );
        return verified.payload;
      } catch (error: any) {
        throw new Error("Your token is expired");
      }
    }

But This code works :

"use client"; 
    import axios from "axios"; 
    import { useEffect, useState } from "react";
    export default function UserDetails() {
      const [userData, setUserData] = useState({
        name: "",
        email: "",
      });
    
      useEffect(() => {
        const getUserDetails = async () => {
          const res = await axios.get("/api/me");
          if (res.status === 200) {
            setUserData({
              name: res.data.data.username,
              email: res.data.data.email,
            });
          }
        };
        getUserDetails();
      }, []);
    
      return (
        <div>
          <h1>{userData.name}</h1>
          <h1>{userData.email}</h1>
        </div>
      );
    }

Solution

  • In the login api, you set the expired time to 1d

    const token = await jwt.sign(tokenData, process.env.JWT_SECRET!, {expiresIn: "1d",});

    it means,if after one day you try to verify it; gives error

    To solve this;

    Erase the cookie from your developement or localhost server {go to profile and logout} login again and then you will not get any error