Search code examples
reactjsnext.jsjwtnext.js13next-auth

Next.js store JWT from external API to use it for calls to that API


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


Solution

  • 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 !! 
    }