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 (3)

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