Node JS, express-session, remove cookie from client browser - node.js

I work with app, that already has its own infrastructure. The task is to prevent user login in several browser. Our application has single app architecture, so ideally user should work only in one browser tab. And I have a problem. I can’t remove cookie from client.
I. Briefly.
App settings:
Server: NodeJS
Port: 8083
Client: VueJS
Port: 8088
I use module express-session to initialize session mechanism on server side and send cookies to client. Client hasn’t set cookies.
II. Details:
Server’s root file is index.js
I do the following in it:
Plug in express module:
const express = require('express')
Plug in cors module:
const cors = require('cors')
Add cors settings:
app.use(cors({
origin: 'http://localhost:8088',
credentials: true
}))
Then I initialize session in user.js file and receive client’s connects:
Plug in express-session module:
const session = require('express-session')
Plug in routing by express.Router():
const router = express.Router()
Add session settings:
const EIGHT_HOURS = 1000 * 60 * 60 * 8
const {
SESS_NAME = 'sid',
SESS_LIFETIME = EIGHT_HOURS,
SESS_SECRET = 'test',
NODE_ENV = 'development'
} = process.env
const IN_PROD = NODE_ENV === 'production'
Initialize session:
router.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized: false,
secret: SESS_SECRET,
cookie: {
maxAge: SESS_LIFETIME,
sameSite: false,
// Must have HTTPS to work 'secret:true'
secure: IN_PROD
}
}))
Receive client queries by router.post()
So what I did:
I use req.session.destroy to remove session data and expect the browser logout user from certain browser and cookies clear.
req.session.destroy(err => {
if (err) {
return res.send({ error: 'Logout error' })
}
res.clearCookie(SESS_NAME, {path: '/'})
return res.send({ 'clearSession': 'success' })
})
Unfortunately nothing magic happens
I read different topics. For example, here (GitHub) offer the conclusion: use explicit cookie’s path indication in res.clearCookie method as shown above.
That didn’t work.
Wrote this setting {path: '/'} in cookies settings. Didn’t work too.
router.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized: false,
secret: SESS_SECRET,
cookie: {
path: '/',
maxAge: SESS_LIFETIME,
sameSite: false,
// Must have HTTPS to work 'secret:true'
secure: IN_PROD
}
}))
And as wrote in express-session documentation (NPM:express-session) this path is the default path for cookie storage.
Add req.session = null in req.session.destroy:
req.session.destroy(err => {
            if (err) {
                return res.send({ error: 'Logout error' })
            }
            req.session = null
            res.clearCookie(SESS_NAME, {path: '/'})
            return res.send({ 'clearSession': 'success' })
        })
That didn’t work
delete req.session doesn’t work too.
So, how can I resolve this problem? What should I do?

adding .send('cleared cookie') made my browser clear its cache of the named cookie.
const logOutRequest = (req, res) => {
req.session.destroy((err) => {
res.clearCookie("notcookie").send('cleared cookie');
});
};

Have you tried removing the exact cookie by setting it to null that is lets say that you are dealing with a cookie named Views you could remove the cookie using req.session.Views = null
Instead of doing this
req.session.destroy(err => {
if (err) {
return res.send({ error: 'Logout error' })
}
req.session = null
res.clearCookie(SESS_NAME, {path: '/'})
return res.send({ 'clearSession': 'success' })
})
you could do the name of your session cookie and set that to null that is
req.session.sid= null

This removes the cookie from client browser
req.session.destroy(err => {
res.clearCookie("session-cookie-name", { path: "/" });
});

The most important key is setting 'domain' in the clearCookie method to solve your issue. Expressjs will return the following in the http response header. However, it seems that on the browser or some browsers that I tested, it doesn't know which cookie belonging to which domain to clear, hence, the cookie remains there. You do not need to include the path when calling clearCookie
Set-Cookie: mycookie=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
You have to set domain like below:
req.session.destroy(err => {
res.clearCookie("session-cookie-name", { domain: 'your-domain' });
});
Then response header will become
Set-Cookie: mycookie=; Domain=your-domain; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
The browser will clear the cookie nicely!

this works for me
#Post('signout')
async signout(#Req() req: Request, #Res({ passthrough: true }) res: Response) {
const user = req.user
if (!user) return {}
await new Promise<void>((resolve, reject) => {
req.session.destroy((err) => {
if (err) {
reject(err)
} else {
res.clearCookie('ACCESS_TOKEN', {
domain: '.xxx.com'
})
res.clearCookie('REFRESH_TOKEN', {
domain: '.xxx.com'
})
res.clearCookie('connect.sid', {
domain: '.xxx.com'
})
resolve()
}
})
})
return {}
}

Related

React - httpOnly cookies not getting set from the express server

Vite + React: http://localhost:5173
Express: http://localhost:3000
Here is the code in the express server's login route:
const mycookie = cookie.serialize("jwt", refreshToken, {
httpOnly: true, // Set the HTTP-only flag
secure: true, // Set the secure flag
sameSite: "none",
path: "/", // Set the path of the cookie to '/'
maxAge: 3600, // Set the maximum age of the cookie to 1 hour
});
// Set the cookie in the response headers
res.setHeader("Set-Cookie", mycookie);
res.json({
accessToken,
refreshToken,
});
Here is my cors config:
const allowedOrigins = require("./allowedOrigins");
const corsOptions = {
crendials: true,
origin: function (origin, callback) {
console.log(allowedOrigins.indexOf(origin));
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
};
Here is my handleSubmit function in the frontend (gets called when the user clicks submit):
const handleSubmit = () => {
console.log("Login");
// axios.defaults.withCredentials = true;
axios.post("http://localhost:3000/api/v1/auth/login", { email, password });
};
Whenever the request is made to the server. The response header does contain the set-Header to set the jwt token but I am not able to see it in my applications tab under cookies in devtools. A pre-flight request also comes in which probably clears the cookie.
My networks tab:
The xhr request:
The OPTIONS request:
The Applications tab:
However, when I disable CORS in the browser, the cookie is getting set.
Networks tab (NOTICE: No PreFlight request)
Applications tab:
I tried working out different answers from stackoverflow answers like this, this, this, and many more along with a reddit post on a similar issue but nothing has worked.
P.S. : I already tried using credentials: true

MERN project: Cann't find the cookies on client side in production environment

Background:
I use express-session to do authetication in my MERN project.
After I logged in, a session will be created and sessionId will be send to client side by cookie. So when I am trying to fetch data, server will verify my sessionId.
I run my project in dev-env, and everything goes well. I can get the cookies in chrome devtools.
BUT after I deployed my project on Vercel(Server and Client depolyed separately). When I login in my web, there are no cookies in devtools.Verification failed, no sessionId returned.
Below are my codes:
server side:
//setup express-session
app.use(
session({
secret: process.env.SESSION_SECRET,
store: store,
resave: false,
saveUninitialized: false,
cookie: {
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "PRODUCTION" ? true : false,
maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week
},
name: "rd_sid",
})
);
//setup cors
const corsOptions = {
origin: process.env.CORS_ORIGIN.split(",").map(
(origin) => new RegExp(origin)
),
allowedHeaders: "Origin, X-Requested-With, Content-Type, Accept",
credentials: true,
};
app.use(cors(corsOptions));
client side:
export default function ajax(url, data = {}, method = "GET") {
return new Promise((resolve, isRejected) => {
let promise;
// 1.1 Execute ajax request
if (method === "GET") {
promise = axios.get(url, {
params: data,
withCredentials: true,
});
} else if (method === "POST") {
promise = axios.post(url, data, { withCredentials: true });
}
promise
.then((response) => {
// 1.2 If success, call resolve(value)
resolve(response.data);
})
.catch((error) => {
// 1.3 If fail, do not call reject(reason) but alert error message.
message.error("request error:" + error.message);
});
});
}
I set withCredentials to ture, and also set the header for cors. So it will solve the cross domain problem.
I try to switch secure , httpOnly, and change the cookie's name. But they seem not work.

How come browser can't store cookies?

Currently, I am on a solo project, but I've got an issue that is the same as the title.
Client-side is on React with HTTPS.
Server-side is on Express with HTTPS. and they are cross-domain.
when the submit button is clicked, the code below is running.
//client side
axios
.post('https://ohmycounty.me/user/signin', {email : "byron#google.com", password: "example", {
withCredentials: true,
})
//server side
app.use(session({
secret: 'example',
resave: false,
saveUninitialized: true,
cookie: {
sameSite: 'none',
secure: true
}
}));
app.use(cors({
origin: ['https://ohmycounty.xyz'], // client app's url
credentials: true
}));
const { users } = require('../../models');
module.exports = {
post: (req, res) => {
console.log(req.session.userid)
const {
email,
password
} = req.body;
let session = req.session;
users.findOne({
where: {
email: email,
password: password
}
}).then(result => {
if (!result) res.status(401).send(JSON.stringify({
status: false
}))
else {
session.userid = result.id;
res.status(201).json({
id: result.id
})
}
})
}
}
I can't find any connect.sid on chrome developer tool applications.
Chrome Applications
Chrome Network CookieHeaders
Waiting for advice from experienced developers.
Any opinion is fine.
Please help me.
The secure attribute should be set if and only if the connection is made over HTTPs. So if you changed it to false, it should work in the dev environment.
cookie: {
secure: false
}
From the express-session doc:
cookie.secure
Specifies the boolean value for the Secure Set-Cookie attribute. When truthy, the Secure attribute is set, otherwise, it is not. By default, the Secure attribute is not set.
Note be careful when setting this to true, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection.
Link to the doc: https://www.npmjs.com/package/express-session

How to set up express-session in production. Express-session is not working in https

I have using express-session and connect-mongo for storing the session and it is working fine in the development environment. However, it is not persisting sessions in production. When I logged in I can get the session on the server by console.log(req.sesssion). However, the session is undefined if I move to another url.
const whitelist = ['https://example.com', 'https://www.example.com'];
const corsOptions = {
origin(origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
app.use(cors(corsOptions));
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
store,
}));
After searching for the answer I have spent about a day. Finally, I found the issues causing all the headaches. The issue was related to http requests. I am using axios for the requests and by default, Axios does not fetch send or receive any cookies. To be able to work cookies I needed to pass Axios config options.{withCredentials: true} for every requests that I made. Thanks to the post author.
Axios.post(url, data, { withCredentials: true })...
However, that lead me to another issue.
"The value of the 'Access-Control-Allow-Credentials' header in the
response is '' which must be 'true' when the request's credentials
mode is 'include'."
Then I have added credentials: true to my cors() module like so.
const corsOptions = {
origin(origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
};
Now My project is live and serving users to make their lives easier :).

Every time React sends request to Express, a new session is generated

I use React as a client to send request to Express with proxy and express-session set up. But every time React makes a request to Express server, a new session is created. So I checked Express alone by manually accessing to the same api url and it keep using the same session each time I refresh the page.
Project structure:
project-folder
- client // React client with proxy set up
+ src
+ package.json
+ ...
- server.js
- package.json
Inside server.js:
const session = require('express-session');
let sessionConf = {
name: 'aoid',
secret: 'stackoverflow',
resave: true,
saveUninitialized: true,
rolling: true,
cookie: {
httpOnly: false,
secure: false,
maxAge: 2000000
}
};
app.use(session(sessionConf));
app.get('/api/prod', (req, res, next) => {
let sessionId = req.sessionID; // is generated each time React client send request, works fine with api alone!
console.log(sessionId);
if (!sessionId) return res.status(401).send('Unauthorized Error');
res.status(200).send({ data });
});
Here is how React client send its request to Express:
let loadItems = async () => {
const response = await fetch('/api/prod');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
}
I think the problem comes from the misconfiguration between React and Express. Did anyone have this problem before?
fetch does not send cookie by default, you need to set it explicitly:
fetch(url, {
method: 'GET',
credentials: 'include',
// ...
})

Resources