Search code examples
authenticationcookieshttp-headersmiddlewarenext.js13

Unable to retrieve cookie after I set it in the browser in next.js13


In Next.js v13 I was trying to work with auth, and I want to store my JWT token in cookie, I was able to store it, but I am unable to retrieve it, I tried it with multiple libraries (for eg: cookie, js-cookie, cookies-next) even tried it with cookie from "next/headers", but still unable to retrieve it in both server components and client components.

"app/api/auth/loginUser/route.js"

import User from "@/app/_models/User";
import connectDB from "@/app/lib/connectdb";
import { AES, enc } from "crypto-js";
import { NextResponse } from "next/server";
import { sign } from "jsonwebtoken";
import { serialize } from "cookie";

const MAX_AGE = 60 * 60 * 24 * 2;

export async function POST(request) {
    try {
        connectDB();

        const { email, password } = await request.json();
        const user = await User.findOne({ email: email });

        if (!user)
            return new NextResponse(
                JSON.stringify({
                    success: false,
                    message: "Invalid credentials",
                })
            );

        const dycPassword = AES.decrypt(
            user.password,
            process.env.CRYPT_SECRET_KEY
        ).toString(enc.Utf8);

        if (dycPassword !== password) {
            return new NextResponse(
                JSON.stringify({
                    success: false,
                    message: "Invalid credentials",
                })
            );
        }

        const token = sign(user.toObject(), process.env.JWT_SECRET_KEY, {
            expiresIn: "2d",
        });

        const setCookie = serialize("userToken", token, {
            httpOnly: true,
            secure: process.env.NODE_ENV === "production",
            sameSite: "strict",
            maxAge: MAX_AGE,
            path: "/",
            httpOnly: false,
        });

        return new NextResponse(
            JSON.stringify({
                success: true,
                message: "Welcome!!!",
            }),
            { status: 200, headers: { "Set-Cookie": setCookie } }
        );
    } catch (error) {
        return new NextResponse(
            JSON.stringify({ success: false, message: error.message }),
            { status: 500 }
        );
    }
}

"app/api/auth/me/route.js"

import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function POST(req) {
    try {
        const cookieStore = cookies();
        const token = cookieStore.get("userToken");

        console.log(cookieStore.getAll(), token);

        return new NextResponse(
            JSON.stringify({ success: true, token: token }, { status: 200 })
        );
    } catch (error) {
        return new NextResponse(
            JSON.stringify({ success: false, message: error.message }),
            { status: 500 }
        );
    }
}

"app/page.jsx"

import React from "react";
import { cookies } from "next/headers";

export const dynamic = "force-static";

export default async function Page() {
    const token = cookies().get("userToken");
    const data = await fetch("http://localhost:3000/api/auth/me", {
        method: "POST",
    }).then((data) => data.json());

    console.log(data);

    return (
        <div>
            <ul>{token}</ul>
        </div>
    );
}

Above code can store the cookie but cant retrieve it. All I get from the console.logs are empty object, array or undefined

Dependency List :

"dependencies": {
    "@headlessui/react": "^1.7.17",
    "@heroicons/react": "^2.0.18",
    "cookie": "^0.5.0",
    "crypto-js": "^4.1.1",
    "jsonwebtoken": "^9.0.2",
    "mongoose": "^7.5.3",
    "next": "latest",
    "next-themes": "^0.2.1",
    "react": "latest",
    "react-dom": "latest",
    "react-switch": "^7.0.0",
    "react-toastify": "^9.1.3",
    "sass": "^1.68.0"
},
"devDependencies": {
    "autoprefixer": "latest",
    "eslint": "latest",
    "eslint-config-next": "latest",
    "postcss": "latest",
    "tailwindcss": "latest"
}

UPDATE :
so I was experimenting with the headers, and it turns out headers are not sent to the page the where I need the cookie, When I make a request with the ThunderClient (VS code extension), i was able to get the cookie i set in the thunderClient but in the browsers like chrome and edge, headers are just empty, their is no cookie or anything, so to check that I created a middleware and console.log the headers, so in the middle ware all the things are present but, they are not being sent to the server component ("app/page.js") where i need them.

middleware.js

import { NextResponse } from "next/server";

// This function can be marked `async` if using `await` inside
export async function middleware(request) {
    console.log(request.headers.get("cookie"));
    return NextResponse.next({
        request: {
            headers: new Headers(request.headers),
        },
    });
}

// See "Matching Paths" below to learn more
export const config = {
    matcher: "/",
};

Headers I get in the "app/page.js" :

HeadersList {
  cookies: null,
  [Symbol(headers map)]: Map(0) {},
  [Symbol(headers map sorted)]: null
}

Headers I get in the "app/api/auth/me/route.js" :

HeadersList {
  cookies: null,
  [Symbol(headers map)]: Map(10) {
    'accept' => { name: 'accept', value: '*/*' },
    'accept-encoding' => { name: 'accept-encoding', value: 'gzip, deflate' },
    'accept-language' => { name: 'accept-language', value: '*' },
    'cache-control' => { name: 'cache-control', value: 'no-cache' },
    'connection' => { name: 'connection', value: 'keep-alive' },
    'content-length' => { name: 'content-length', value: '0' },
    'host' => { name: 'host', value: 'localhost:3000' },
    'pragma' => { name: 'pragma', value: 'no-cache' },
    'sec-fetch-mode' => { name: 'sec-fetch-mode', value: 'cors' },
    'user-agent' => { name: 'user-agent', value: 'undici' }
  },
  [Symbol(headers map sorted)]: [
    [ 'accept', '*/*' ],
    [ 'accept-encoding', 'gzip, deflate' ],
    [ 'accept-language', '*' ],
    [ 'cache-control', 'no-cache' ],
    [ 'connection', 'keep-alive' ],
    [ 'content-length', '0' ],
    [ 'host', 'localhost:3000' ],
    [ 'pragma', 'no-cache' ],
    [ 'sec-fetch-mode', 'cors' ],
    [ 'user-agent', 'undici' ]
  ]
}

Also, this is how I am redirecting to home page :

const handleSubmit = async (e) => {
    e.preventDefault();

    if (email == "" || password == "") {
        toast.error("Fill all the fields");
        return;
    }

    const res = await fetch("api/auth/loginUser", {
        method: "POST",
        cache: "no-store",
        body: JSON.stringify({ email, password }),
    }).then((data) => data.json());

    if (res.success) {
        toast.success(res.message);
        router.push("/");  // <-------------redirect
    } else {
        toast.error(res.message);
    }
};

This is how cookie is stored in the edge : Image of cookie stored in the edge


Solution

  • You cannot use export const dynamic = 'force-static' and cookies() at the same time. According to this NextJS docs page:

    Force static rendering and cache the data of a layout or page by forcing cookies(), headers() and useSearchParams() to return empty values.

    The only solution for you is to either change the method (maybe use sessionStorage for storing the token?) or rendering to something different than force-static. Although I'm well aware that force-static has nice advantages for loading time (since the page is fully static and can be cached), however any usage of headers(), cookies() etc. is pointless, as empty values will be returned.