Search code examples
authenticationnext.jscookiessession-cookiesnext-auth

'Session cookie exceeds allowed 4096 bytes.'-getting this Next Auth error after upgrading nextJs 14.1.4 from 14.1.0


I am using nextJs as frontend and express as backend. for authentication , I am using Next-Auth.

It was still fine when I upgraded nextJs 14.1.0 from 13.5 but yesterday I updated my next app into latest till now (v14.1.4) and I was getting a auth api error. Then again, I reverted into the previous version (14.1.0) and got the error solved. But got a new error while trying to log in.

In console, I am getting this: next auth error

The issue is, too many Next-auth session cookies is being set (here 7) to session storage. error

here's my authoptions:

import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { AuthOptions } from "next-auth";
import prisma from '@/libs/prismadb';
import bcrypt from "bcrypt";
import  CredentialsProvider  from 'next-auth/providers/credentials';

export const authOptions:AuthOptions = {
    adapter: PrismaAdapter(prisma),
    providers: [
        CredentialsProvider({
            name: 'credentials',
            credentials:{
                email: { label: 'email', type: 'text' },
                password: { label: 'password', type: 'text'},
            },
            async authorize(credentials) {

                if (!credentials?.email || !credentials.password) {
                    throw new Error(`Invalid credentials`)
                }

                const user = await prisma.user.findUnique({
                    where: {
                        email: credentials.email
                    }
                });
                if (!user || !user.hashedPassword) {
                    throw new Error(`Invalid credentials`);
                }

                const isCorrectPassword = await bcrypt.compare(credentials.password, user.hashedPassword);

                if (!isCorrectPassword) {
                    throw new Error(`Incorrect password`)
                }

                return user;

            }
        })
    ],
    debug: process.env.NODE_ENV === 'development',
    session: {
        strategy: 'jwt'
    },
    secret: process.env.NEXTAUTH_SECRET
}

I my Next-Auth folder path is :

src/app/api/auth/[...nextauth]/route.ts

and the route.ts includes :

import NextAuth from 'next-auth/next';
import { authOptions } from '@/utils/authOptions';


const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

How may I solve the issue? Or is there any good resource to authenticate using my external express server?

I tried rebuilding the app and discard all the changes after upgrading my nextJs app into latest version.


Solution

  • One workaround is to use Callbacks

    SHORT ANSWER: Save only what you need in your session to avoid this warning.

    I share an example with you. There is no prisma adapter in this one but the logic would be similar, please take this as a reference.

    In this example, the login endpoint response object includes

    • id
    • accessToken
    • refreshToken
    • idToken

    When we consider the token sizes, it is perfectly normal to get this warning as a result. To overcome this issue, we save only what we need in our session. To do this, we add two callback methods like below.

    callbacks: {
        async jwt({ token, user, account }) {
            // I advise you to log those variables here to see what you get
            if (account && user) {
                token.id = user.id
                token.accessToken = user.accessToken
                token.refreshToken = user.refreshToken
    
                // idToken may include some user information such as email, I leave this piece of code as an example to understand how we can extract information from it
                const decodedIdToken = JSON.parse(Buffer.from(user.idToken.split(".")[1], "base64").toString())
    
                if (decodedIdToken) {
                    token.email = decodedIdToken["email"]
                }
            }
    
            const { refreshToken, ...rest } = token // we do not return refreshToken here because it would increase the session cookie size
            return rest
        },
        async session({ session, token }) {
            return {
                ...session,
                user: {
                    ...session.user,
                    id: token.id as string,
                    email: token.email as string,
                    accessToken: token.accessToken as string,
                },
                error: "" // long story, I will not go into the details about this now
            }
        }
    }
    

    Reference: https://authjs.dev/guides/basics/callbacks

    Of course, to prevent typescript complains, we need to add next-auth.d.ts file under the path /types (types folder is at the root of the project)

    import { DefaultSession } from "next-auth"
    
    declare module "next-auth" {
        interface User {
            id: string
            email: string
            accessToken: string
            refreshToken: string
            idToken: string
        }
    
        interface Session {
            user: User & DefaultSession["user"]
            expires: string
            error: string
        }
    }
    

    Reference: https://authjs.dev/getting-started/typescript

    As you see, we can extract whatever info we need. We excluded refreshToken from the session cookie in this example. Someone may ask "So how can I access my refreshToken once my accessToken expired? We do not have it in the session". The answer is using cookies

    After a successful login, we can save our refreshToken as an http only cookie

    async authorize(credentials) {
        const payload = {
            email: credentials.email,
            password: credentials.password,
        }
        const res = await fetch(`${process.env.API_BASE_URL}/auth/login`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Access-Control-Allow-Origin": "*",
            },
            body: JSON.stringify(payload)
        })
    
        const user = await res.json()
    
        if (!res.ok) {
            throw new Error(user.message)
        }
    
        if (res.ok && user) {
            cookies().set({
                name: `refresh-token`,
                value: user.refreshToken,
                httpOnly: true,
                sameSite: "strict",
                secure: true,
            } as any)
    
            return user
        }
        
        return null
    }