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.
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));
};
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,
})
);
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" });
}
});
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")
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",
});
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",
}
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;
const expires = new Date(Date.now() + 60 * 60 * 1000);
expires: expires,
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"]```
Top comments (6)
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,
});
-------- 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);
} catch (error) {
console.log(error);
}
};
-------- 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.
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"]
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!
"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
Thanks a lot for stopping by and enlightening us. Will definitely take care and update it
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