I have a React app where I use Axios to communicate with an Express.js backend. The flow for generating "access tokens" is as follows:
The issue at stake:
While trying to send the request to refresh the "access token", I didn't see the "refresh token" cookie attached to the headers, and of course, I got error 401. On Postman, it works just fine. It saves the cookie and I can see it in every request - Including the request to regenerate an "access token". I tried the following code setup on both "localhost" and the actual domain.
this is the Current code setup:
-- REACT | axios interceptor inside the AxiosConfig --
if (err.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
if (typeof window !== "undefined") {
const cookies = new Cookies();
const { data } = await axios.post(
`path/to/endpoint`,
{},
{
withCredentials: true,
}
);
if (data.accessToken) {
const newAccessToken = data.accessToken;
cookies.set("access_token", newAccessToken, {
path: "/",
secure: true,
sameSite: "strict",
});
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return _axios(originalRequest);
} else {
throw new Error("Refresh token failed: No accessToken.");
}
}
} catch (refreshError) {
console.error("Failed to refresh token:", refreshError);
window.location.href = "/login";
return Promise.reject(refreshError);
}
}
-- Backend | Express.js --
The backend uses jsonwebtoken to generate tokens and this is the part where it sets the cookie to the response headers:
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env.APP_ENV === 'production',
sameSite: 'Strict',
maxAge: 90 * 24 * 60 * 60 * 1000 }
This is the response headers coming from the "sign in" request:
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Tue, 24 Dec 2024 17:01:14 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 340
Connection: keep-alive
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
Access-Control-Allow-Credentials: true
Set-Cookie: refreshToken="GENERATED TOKEN"; Max-Age=7776000; Path=/; Expires=Mon, 24 Mar 2025 17:01:14 GMT; HttpOnly; SameSite=Strict
and these are the request headers from the "refresh token" request:
POST /path/to/endpoint HTTP/1.1
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 2
Content-Type: application/json
Host: server-domain
Origin: http://localhost:5173
Referer: http://localhost:5173/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
and these are the headers from Postman: screenshot attached by SO
What I've Tried:
Question: Why is the refreshToken cookie not being sent with my Axios requests, despite using withCredentials? Is there something I’m missing in my frontend or backend configuration?
When implementing the login request on the frontend, ensure the credentials option is included in the Axios request to allow cookies or authentication tokens to be sent along with the request. For example:
const response = await axios.post('your-login-endpoint',{'your-body-data'},{withCredentials: true});