skip to Main Content

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…

2

Answers


  1. Chosen as BEST ANSWER

    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);      
        }
    }
    

  2. first create file for ActionServer:

    ServerActions.ts

    "use server";
    
    export async function setToken(token: string) {
      cookies().set("token", tok);
    }
    
    export async function getToken() {
      const token = cookies().get("token");
      return token?.value;
    }
    

    you can get server side:

    page.ts:

      const token = await getToken();
    
    

    and for client :

    const test = async = ()=> {
      const token = await getToken();
      // you do anyting you want 
      // you can use this in useEffect !! 
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search