I'm developing the frontend of an application with NextJS 14. I have configured a CredentialsProvider with NextAuth, user provides email and password and it is sent to the Backend (SpringBoot) to authenticate them and the Backend sends a JWT if the authentication is successful.
I store the access token in a cookie to use it when I make a request to the backend (using an Authentication header) but I'm not sure if there's a better approach. And in some pages it is causing problems to retrieve the cookie.
Can I store it in the session? If so, how?
This is my credentials provider:
import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { cookies } from "next/headers";
const loginPath: string = process.env.NEXT_PUBLIC_LOGIN_PATH!;
const parsePayload = (token: string) => {
try {
return JSON.parse(atob(token.split('.')[1]));
} catch (e) {
return null;
}
};
export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: {label: "Username", type: "text"},
password: {label: "Password", type: "password"},
},
async authorize (credentials) {
const {username, password} = credentials as {
username: string;
password: string;
};
const formData = new URLSearchParams();
formData.append("email", encodeURIComponent(username));
formData.append("password", encodeURIComponent(password));
const res = await fetch (loginPath, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: formData.toString(),
});
const receivedToken = await res.json();
if (res.ok && receivedToken) {
const user = receivedToken;
const parsed = parsePayload(receivedToken.access_token);
user.name = parsed.name;
cookies().set({
name: "gat",
value: receivedToken.access_token,
httpOnly: true});
cookies().set({
name: "grt",
value: receivedToken.refresh_token,
httpOnly: true});
return user;
} else {
return null;
}
},
}),
],
callbacks: {
jwt: async ({token, user}: {token: any, user: any}) => {
if (user) token = user as unknown as {[key: string]: any};
return token;
},
session: async ({ session, token }: {session: any, token: any}) => {
session.user = { ...token }
return session;
},
},
pages: {
signIn: "/login",
},
};
The problem is that I have access to that "gat" cookie only from server components using:
import { cookies } from 'next/headers'
console.log("GAT: " + cookies().get("gat")?.value);
And if I need to make a call from a client component I need to pass that value to the client component as a parameter, which I find pretty awful...
In the end I solved it defining API Routes.
Instead of calling the SpringBoot backend from client components, I defined API Routes (https://nextjs.org/docs/pages/building-your-application/routing/api-routes) so I don't need to pass the cookie content to client components.
For example:
/api/session/route.tsx
import { cookies } from 'next/headers'
import { type NextRequest } from 'next/server';
const url = `${process.env.NEXT_PUBLIC_SESSION_PATH}`
export async function POST(req: Request) {
const { session } = await req.json();
return await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + cookies().get("gat")?.value
},
body: session
});
}
and from client components I call this function as follows:
const handleSubmit = async(e: React.FormEvent) => {
e.preventDefault();
const requestOptions = {
method: "POST",
headers: {
Authorization: "Bearer " + gat,
"Content-Type": "application/json",
},
body: JSON.stringify(formValues),
};
const result = await fetch('/api/session', requestOptions);
if (result.status == 201) {
console.log('Se creó la sesión con éxito');
setSuccess("success");
} else {
setError("error");
console.log('Error al crear la sesión ' + result.statusText);
}
}