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.
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
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
}