admin管理员组

文章数量:1129677

My React Next.JS Application calls a REST API External and receives tokens in Cookies for authentication (Access and Refresh Tokens), like this picture below:

.png

I use Server Actions to fetch this REST API, maintaining sensitive information such as login and password on the server side.

In my Server Action I set those cookies in my client and send those cookies to each request the REST API in the request header, like this:

const proxyServerCookies = (cookieNames, response) => {
    if (response.headers.has('set-cookie')) {
        const cookieString = response.headers.get('set-cookie');
        const cookieObject = setCookieParser.parse(setCookieParser.splitCookiesString(cookieString), {
            map: true,
        });

        cookieNames.forEach(async (cookieName) => {
            if (cookieObject[cookieName]) {
                const cookie = cookieObject[cookieName];

                await cookies().set(cookieName, cookie.value, {
                    //The problem is right here. If i put the line below the cookie in path /auth, like I received from the REST API, it does not show in the browser client.
                    //path: cookie.path, 
                    path: '/',   
                    maxAge: cookie.maxAge,
                    sameSite: (cookie.sameSite),
                    expires: cookie.expires,
                    secure: cookie.secure,
                    httpOnly: cookie.httpOnly,
                });
            }
        });
    }
}

The problem is that the refreshToken cookie that I receive from the REST API comes with the path /auth and keeping this path the Action Server cannot write to the client, or at least I don't see it in the browser's cookie list.

When I change the path to root / the cookie recording works.

Could anyone tell me why this behavior?

My React Next.JS Application calls a REST API External and receives tokens in Cookies for authentication (Access and Refresh Tokens), like this picture below:

https://i.sstatic.net/TMa5rcuJ.png

I use Server Actions to fetch this REST API, maintaining sensitive information such as login and password on the server side.

In my Server Action I set those cookies in my client and send those cookies to each request the REST API in the request header, like this:

const proxyServerCookies = (cookieNames, response) => {
    if (response.headers.has('set-cookie')) {
        const cookieString = response.headers.get('set-cookie');
        const cookieObject = setCookieParser.parse(setCookieParser.splitCookiesString(cookieString), {
            map: true,
        });

        cookieNames.forEach(async (cookieName) => {
            if (cookieObject[cookieName]) {
                const cookie = cookieObject[cookieName];

                await cookies().set(cookieName, cookie.value, {
                    //The problem is right here. If i put the line below the cookie in path /auth, like I received from the REST API, it does not show in the browser client.
                    //path: cookie.path, 
                    path: '/',   
                    maxAge: cookie.maxAge,
                    sameSite: (cookie.sameSite),
                    expires: cookie.expires,
                    secure: cookie.secure,
                    httpOnly: cookie.httpOnly,
                });
            }
        });
    }
}

The problem is that the refreshToken cookie that I receive from the REST API comes with the path /auth and keeping this path the Action Server cannot write to the client, or at least I don't see it in the browser's cookie list.

When I change the path to root / the cookie recording works.

Could anyone tell me why this behavior?

Share Improve this question asked Jan 8 at 8:49 JoaoJoao 115 bronze badges New contributor Joao is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 10
  • just a guess: accessToken is used for domain-wide access (after successful authentication on /auth) while refreshToken is only used for accessToken renewal (which is done with a request to /auth) – mr mcwolf Commented Jan 8 at 8:57
  • Exactly. My question is why, when NextJS tries to save the token received from the REST API with the path /auth on the client, it does not appear in the client's Browser? If I try to fetch directly from the client component, without the server action, the REST API normally records the AccessToken with the path / and the RefreshToken with the path /auth. – Joao Commented Jan 8 at 23:44
  • Now I understand. How do you know that the cookie "not appear" in the browser? Check the header of the request that should set the cookie. You should have Set-Cookie headers for both cookies Set-Cookie: accessToken=value; path=/; ... and Set-Cookie: refreshToken=value; path=/auth; .... Show them so we can see if the server is sending them at all and what parameters they have (the values ​​are not needed, you can hide them with fake ones). – mr mcwolf Commented Jan 9 at 6:17
  • Hi buddy... About the first question,I know because I don't see him in the section DevTools (Chrome) - Application - Storage - Cookies in the client browser, I just see him when I put the path '/', otherwise when I try to search for it through the server action, it comes as undefined. This does not occur with the AccessToken that is in path '/' or when I save the RefreshToken in parth '/' – Joao Commented Jan 9 at 15:25
  • Sorry buddy, you are right, I didn't know you could see the header of the request in devTools. In section Network, I can see in the request page both the cookies, thx. Why just the AccessToken cookie in the path "/" appears in the Cookie section? – Joao Commented Jan 9 at 15:34
 |  Show 5 more comments

1 Answer 1

Reset to default 0

I was a little confused with how cookies work, but my friend @mr mcwolf helped me review the concepts and the solution to the problem follows:

I make requests to an external REST API that returns two access cookies in the response, an AccessToken in the '/' path and the RefreshToken in the '/auth' path (The address to use the Refresh Token is https://api_domain/auth/refresh-token).

In my NextJS client, I basically use interceptors to check when the AccessToken has expired and use RefreshToken to update it.

However, in the first attempt I do not have AccessToken or RefreshToken, making it necessary to use the Login/Password credentials to log into the REST API, which is why I used NextJS's Actions Servers, to leave this sensitive data only on the server side.

Therefore, I had to manually write and read cookies through the Action Server as requests would be made from them. I simply took the cookies coming from the REST API (AccessToken and RefreshToken) and created a copy of the client's browser, via the Action Server. According to the post code.

When the client tried to access an address on my website (https://my_domain/app/access), the Server Action was asked to take care of it, sending the request to the REST API. The access token has expired and the Action Server must use the RefreskToken to refresh in the REST API at https://api_domain/auth/refresh-token. This is where I got confused, the Action Server was unable to read the contents of this RefreshToken cookie as it was sent via the /app subdomain, therefore it did not reach the Action Server at that time and was not able to relay it to the REST API at https:// /api_domain/auth/refresh-token.

As the only sensitive data in this scenario are the Login/Password credentials for the first access to obtain the tokens, I placed only this in an Action Server to be handled on the server side, and kept the interceptors as client components, therefore REST API records cookies directly on the client and the RefreshToken in '/auth' will be sent correctly to https:// /api_domain/auth/refresh-token.

//apiLogin.js
'use server';

import axios from 'axios';
import setCookieParser from 'set-cookie-parser';
import { cookies } from 'next/headers';

const proxyServerCookies = (cookieNames, response) => {
    if (response.headers.has('set-cookie')) {
        const cookieString = response.headers.get('set-cookie');
        const cookieObject = setCookieParser.parse(setCookieParser.splitCookiesString(cookieString), {
            map: true,
        });

        cookieNames.forEach(async (cookieName) => {
            if (cookieObject[cookieName]) {
                const cookie = cookieObject[cookieName];

                await cookies().set(cookieName, cookie.value, {
                    path: cookie.path,
                    domain: cookie.domain,
                    maxAge: cookie.maxAge,
                    sameSite: (cookie.sameSite),
                    expires: cookie.expires,
                    secure: cookie.secure,
                    httpOnly: cookie.httpOnly,
                });
            }
        });
    };
};

export const apiLogin = async () => {
    const response = await axios.post(process.env.NEXT_PUBLIC_API_APP + '/auth/login',
        {
            name: process.env.API_USERNAME,
            password: process.env.API_PASSWORD
        });
    proxyServerCookies(['accessToken', 'refreshToken'], response);
}
//axiosPrivate.js
'use client';

import axios from 'axios';
import { apiLogin } from './apiLogin';

const axiosPrivate = axios.create({
    baseURL: process.env.NEXT_PUBLIC_API_APP,
    withCredentials: true
});

axiosPrivate.interceptors.request.use(
    async (config) => {
        config.timeout = 5000;
        return config;
    }, (error) => Promise.reject(error)
);

axiosPrivate.interceptors.response.use(
    response => response,
    async (error) => {
        const prevRequest = error?.config;
        if (error.response?.status === 401 &&
            (error.response.data?.code === 'AccessTokenExpired' || error.response.data?.code === 'AccessTokenNotFound')
        ) {
            try {
                await axios.post(process.env.NEXT_PUBLIC_API_APP + '/auth/refresh-token', {},
                    { withCredentials: true }
                );
                return axiosPrivate(prevRequest);
            } catch (error) {
                try {
                    //Here is the call to Action Server with sensitive data that will be processed on the server side.
                    await apiLogin();
                    return axiosPrivate(prevRequest);
                } catch (error) {
                    return Promise.reject(error);
                };
            };
        };
        return Promise.reject(error);
    }
);

export default axiosPrivate;

本文标签: nextjs13SetCookie in Server Action doesn39t work with path authStack Overflow