DEV Community

Atul Anand Oraon
Atul Anand Oraon

Posted on

Setting the cookies using the JS, axios and expressJS

Hi all
recently I am learning about the auth.
I tried setting the accessToken and refreshToken for the auth.

Initially decided to use fetch for it. It did not work in my case. Exhausted all the combination of credentials.

credentials:true,
credentials:include,
etc.
Enter fullscreen mode Exit fullscreen mode

I was really pissed and sad for like 3-4 days.

Finally decided to switch to axios also made some adjustment to my backend and it started working.

Let me show you my code.

My axios request

 const loginCall = () => {
// You may get these data from the relevant selector
let data= {
        email: "dummy@mail.com", 
        password: "123",
      }
    const options = {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      withCredentials: true, // Ensure Axios includes cookies in the request
    };

    axios("http://localhost:8002/auth/login", data,options)
      .then((response) => {
        // Check if response status is in the 200 range
        if (response.status >= 200 && response.status < 300) {
          console.log("response is: ", response);

          // Print all headers from response
          console.log("headers are: ");
          console.log(response.headers);

          let responseData = response.data;
          console.log(responseData);

          let accessToken = responseData.accessToken;
          console.log("access token is: ", accessToken);

          // Set the accessToken cookie correctly
          document.cookie = `accessToken=${accessToken}; path=/; SameSite=Strict; expires=${new Date(
            Date.now() + 1000 * 60 * 5
          ).toUTCString()}`;
          console.log("cookie is: ", document.cookie);

          // The refreshToken HttpOnly cookie will be automatically stored by the browser
        } else {
          throw new Error("Network response was not ok");
        }
      })
      .catch((err) => console.error(err));
  };
Enter fullscreen mode Exit fullscreen mode

Put this in your index.js/server.js

const express = require("express");
const app = express();
let cors = require("cors");
app.use(
  cors({
    origin: "localhost",
    credentials: true,
  })
);
Enter fullscreen mode Exit fullscreen mode

To add multiple domains do this.
origin: ["http://localhost:3000", "http://example.com", "https://subdomain.example.com"]

now the below code in your

let express = require("express");
let bcrypt = require("bcrypt");
let jwt = require("jsonwebtoken");
let User = require("../models/user-model");

// Login Route with JWT
router.post("/login", async (req, res) => {
  try {
    const { email, password } = req.body;

    // Check if user exists
    const user = await User.findOne({ email });

    if (!user) {
      return res
        .status(401)
        .json({ message: "Authentication failed, email not found" });
    }

    // Check password
    // @ts-ignore
    const isMatch = await bcrypt.compare(password, user.password);

    if (!isMatch) {
      return res
        .status(401)
        .json({ message: "Authentication failed, wrong password" });
    }

    // Generate JWT token with expiration time of 1 month (in seconds)
    // jwt.sign({ id user._id, exp Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 30) }, SECRET);
    //@ts-ignore
    const { accessToken, refreshToken } = signToken(user);

    // httpOnly token way for the deployment
    // res.cookie("accessToken", accessToken, {
    //   httpOnly: false,
    //   sameSite: "none",
    // });
    const expires = new Date(Date.now() + 60 * 60 * 1000);
    res.cookie("refreshToken", refreshToken, {
      httpOnly: true,
      expires: expires,
      sameSite: "none",
    });

    res
      .status(201)
      .json({ message: "User created successfully", accessToken: accessToken });
  } catch (error) {
    console.log(error);
    res.status(500).json({ message: "Server Error" });
  }
});
Enter fullscreen mode Exit fullscreen mode

Pardon me for a lot of commented code.

I have removed some yet some remains.

So for me this code worked in the firefox browser

    const expires = new Date(Date.now() + 60 * 60 * 1000);
    res.cookie("refreshToken", refreshToken, {
      httpOnly: true,
      expires: expires,
      sameSite: "none",
    });
   res.send("refresh Token sent")
Enter fullscreen mode Exit fullscreen mode

For my friend using Brave browser the below code worked.

const expires = new Date(Date.now() + 60 * 60 * 1000);
    return res
      .status(200)
      .cookie("refreshToken", refreshToken, {
        path: '/',
        httpOnly: true,
        secure: true,
        expires: expires,
        sameSite: "none",
        domain: "localhost",
      })
      .json({
        accessToken: accessToken,
        id: user._id,
        role: user.role,
        firstname: user.firstname,
        lastname: user.lastname,
        email: user.email,
        message: "User logged In Successfully",
    });
Enter fullscreen mode Exit fullscreen mode

Now let me explain you these settings
First of all you must add the origin and credentials. Its a must. Else your tokens won't be sent or set.

const expires = new Date(Date.now() + 60 * 60 * 1000);
    res.cookie("refreshToken", refreshToken, {
        path: '/',
        httpOnly: true,
        secure: true,
        expires: expires,
        sameSite: "none",
        domain: "localhost",
      }
Enter fullscreen mode Exit fullscreen mode

Now these cookie settings.

path: / This means the cookie is valid across all the path.
If you want this to be valid to certain parts only say

/protected

httpOnly:true This option is very crucial here. It tells the browser don't allow any JS touch it. Which means its totally secure. This is protected from the Cross Origin Attack

secure:true This option means that browser will only send this cookie over https connection not http.
My reccomendation is to make a variable named cookieOptions or refreshTokenOptions.

Try setting the secure:true in the production environment.
As you will go nuts when your backend on local system does not
get the cookies. As almost all of us have our local working on http.

let cookieOptions = {
    httpOnly: true,
    sameSite: "none",
  }

if(process.env.NODE_ENVIRONMENT=="production")
  cookieOptions.secure= true;
Enter fullscreen mode Exit fullscreen mode
const expires = new Date(Date.now() + 60 * 60 * 1000);
expires: expires,
Enter fullscreen mode Exit fullscreen mode

This sets the expiry time for the cookie to 1 hour.

You can set the time as per your choice.

My personal reccomendations are 3hour to a day.

Not more than that.

Also the time has to be given in miliseconds.
So to set 1 hour = 60mins X 60sec X 1000 milisec

sameSite: "none" is used to send the cookies cross-site requests,

which basically means your backend and client can be on different ports and even different addresses.

domain: "localhost" means its valid for only for localhost domain.
You may want to have multiple domains do it like this.

    domain: ["localhost", "example.com", ".subdomain.example.com"]```



Enter fullscreen mode Exit fullscreen mode

Top comments (6)

Collapse
 
faisalfasi profile image
Faisal Rehman

Hi Man,

Thank you for explaining this in detail. Could you please tell me how i can send cookie from FE to BE using axios?

I am using react for FE and express for BE.

Here is my express server :

import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import setupSocket from "./lib/socket.js";
import http from "http";
import dotenv from "dotenv";

// Load environment variables from .env file
dotenv.config();

// Routes import here
import postRoute from "./routes/post.route.js";
import authRoute from "./routes/auth.route.js";
import testRoute from "./routes/test.route.js";
import userRoute from "./routes/user.route.js";
import chatRoute from "./routes/chat.route.js";
import messageRoute from "./routes/message.route.js";

const app = express();
app.set("trust proxy", 1);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const cookieParserSecret = process.env.JWT_SECRET_KEY; // Secret key for signing cookies
app.use(cookieParser(cookieParserSecret));

app.use(
cors({
origin: process.env.CLIENT_URL || "localhost:5173", // Replace with your client domain
// methods: ["GET", "POST", "PUT", "DELETE"], // Uncomment to restrict HTTP methods
credentials: true, // Allow credentials (cookies)
})
);

app.use("/api/auth", authRoute);
app.use("/api/users", userRoute);
app.use("/api/posts", postRoute);
app.use("/api/test", testRoute);
app.use("/api/chats", chatRoute);
app.use("/api/messages", messageRoute);

const server = http.createServer(app); // Create HTTP server using Express app
const io = setupSocket(server); // Setup socket.io

const PORT = process.env.PORT || 8800;
server.listen(PORT, () => {
console.log(Server is running on port ${PORT});
});

-------- express server ends here -------------

here is my code for setting the Cookie in the BE:

res
.cookie("token", token, {
withCredentials: true,
httpOnly: true,
sameSite: "none",
secure: process.env.NODE_ENV === "production", // Set secure only in production
maxAge: age,
path: "/",
domain: process.env.CLIENT_URL || "localhost:5173",
})
.status(200)
.json(userInfo);

-------- Cookie code ends here -------------

Here is my FE axios code :

import axios from "axios";

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

export default apiRequest;
-------- axios baseURL ends here -------------

here is how i making request
try {
const response = await apiRequest.post("/auth/login", {
username,
password,
});

  updateUser(response.data);
  navigate("/");

} catch (err) {
  console.log(err.response.data.message);
  setError(err.response.data.message);
} finally {
  setLoading(false);
}
Enter fullscreen mode Exit fullscreen mode

-------- FE making post request ends here -------------

here is how i am trying to verify my token:
import jwt from "jsonwebtoken";

export const verifyToken = (req, res, next) => {
const token = req.cookies.token;
console.log("Token on backend: ", token);

if (!token) {
return res.status(401).json({
message: "You are not authorized!",
});
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY);

req.userId = decoded.id; // Extract user ID from decoded token

next(); // Proceed to the next middleware
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.log(error);

return res.status(403).json({
  message: "Token is not valid!",
});
Enter fullscreen mode Exit fullscreen mode

}
};
-------- verify token ends here -------------

On localhost everything works fine but when i deploy my BE set the cookie but FE doesn't send the coockie to the BE.

i struggled alot and already tried many ways and spend many days on it. but nothing fixed my problem.

Please help me.
i will really be thankful for any lead or help.

Collapse
 
oatula profile image
Atul Anand Oraon


domain: process.env.CLIENT_URL || "localhost:5173",

This is the line I suspect. Might sound silly, but try console.log(process.env.CLIENT_URL) .

The other thing I would suspect is that, the client and server are on different domains or say subdomains.

Try this, because if it is not a cors issue, then definitely the domain of client and server are different. In case the backend doesn't have been cname or DNS to the domain or a subdomain. You would need to do that.

Add in the front-end and back-end domain or subdomain like this and check.

domain: ["localhost", "example.com", "subdomain.example.com"]

Collapse
 
faisalfasi profile image
Faisal Rehman • Edited

Hi @oatula ,

Thank you so much for your response. I tried this already, but it didn't fix the issue.

Here is my domain name for the frontend:
fr-real-estate-1.onrender.com

And here is my domain name for the backend:
fr-real-estate.onrender.com

Here are my cookie settings:

res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production", // Ensure HTTPS in production
maxAge: age, // Cookie expiry time
sameSite: "none",
path: "/", // Root path
domain: process.env.CLIENT_URL, // as you said after console it prints ----> fr-real-estate-1.onrender.com

});

console.log("cookie domain name", process.env.CLIENT_URL);
res.status(200).json(userInfo);

The problem is, when I change,
sameSite: "lax" to sameSite: "none",

my cookie is not being set after login. However, when I set sameSite: "lax", my cookie is being set, but it gets destroyed or is missing when I reload the page.

Can you please tell me what I should do in this case?

More i can login is this mean its not cors issue ?

but after login i get these below errors :

GET fr-real-estate.onrender.com/api/us... 401 (Unauthorized)

Error fetching notifications:
 {message: 'Request failed with status code 401', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}code: "ERR_BAD_REQUEST"config: {transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}message: "Request failed with status code 401"name: "AxiosError"request: XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: true, upload: XMLHttpRequestUpload, …}response: {data: {…}, status: 401, statusText: '', headers: Ds, config: {…}, …}stack: "AxiosError: Request failed with status code 401\n at p1 (fr-real-estate-1.onrender.com/asse... at XMLHttpRequest._ (fr-real-estate-1.onrender.com/asse... at is.request (fr-real-estate-1.onrender.com/asse... at async fetch (fr-real-estate-1.onrender.com/asse... Error

Can you please tell me where should i look for the fix. i am completely losted at this point. Your help will really be appreciated,

Thank you again!

Collapse
 
nigel447 profile image
nigel447 • Edited

"httpOnly:true This option is very crucial here. It tells the browser don't allow any JS touch it. Which means its totally secure. This is protected from the Cross Origin Attack"

The HttpOnly only stops js from reading the cookie not from sending it typically to a hostile endpoint that can access the session, for this attack to succeed you need "sameSite: "none" " which is basically bad

you should use sameSite -> lax | strict to be safe

nothing is totaly secure, code defensivly

Collapse
 
oatula profile image
Atul Anand Oraon

Thanks a lot for stopping by and enlightening us. Will definitely take care and update it

Collapse
 
nigel447 profile image
nigel447

hi atul

your article is good, only issue is you are vulnerable to csrf attacks if u keep
sameSite: "none"

security is hard and we are all learning all the time