Express session resets on every request - node.js

I have a vue frontend on localhost:8080 and a server at localhost:1234
I am using no https.
Every time the vue app switches pages or gets reloaded, the Session resets. I've followed various solutions on the web, like these:Express session resets session on every request and Session keeps resetting on Node.js Express
However to no avail.
This is my session config:
// app.set('trust proxy', 1 );
app.use(session({
secret: sessionServerToken,
resave: true,
saveUninitialized: true,
cookie: {
// Session expires after 1 hour of inactivity.
expires: 60 * 1000 * 60,
// sameSite: 'none',
secure: false
}
}));
and this is my auth code:
router.post('/auth', (req, res) => {
console.log(req.body);
const session = req.session;
AuthManager.authenticate(req.body.user, req.body.pass).then(response => {
session.loggedIn = true;
session.userID = response.user.id;
res.status(200).send(response);
res.end();
}).catch(response => {
res.status(response.statusCode).send({ message: response.message });
});
});

Cookies won't be shared between different origins. Session data is not shared to the frontend app that's why it acts like the session is being reset.
If you build your Vue app and serve it over Express you won't face this problem and the session will be shared as expected.
However for development, it will be annoying, you can proxy the front-end app over Express.
As a minimal example, you can try the code below. Run your Vue app as you normally do and then run the server with the following
const express = require('express');
const session = require("express-session");
const proxy = require('express-http-proxy');
const app = express();
app.use(
session({
secret: 'keyboard cat',
cookie: {
maxAge: 60000
},
value: 0
})
);
app.get('/api', (req, res) => {
req.session.value ++;
res.json({
session: req.session.value
});
});
app.use(proxy('http://127.0.0.1:8080'));
app.listen(4001, '0.0.0.0', () => {
console.log('Server is running at http://127.0.0.1:1234');
});
Vue app content will be available on http://127.0.0.1:1234 and if you navigate http://127.0.0.1:1234/api and refresh several times you will see the session value is present and not resetting.

Related

Cookie not being sent to AWS ECS server from ReactJS

I am current deploying a MERN Stack application and have successfully deployed the backend api to http://44.198.159.229/. I am now trying to connect it to my client server which is still running on localhost:3000. However, I am running into a cookie related issue. I am receiving the cookie on my frontend from the backend express server, but upon making a get request an authenticated route the frontend is not sending the cookie back. In the network tag in google chrome I see that the cookie is instead filtered out. I have done countless research and browsed various posts but cannot seem to find the solution for this. It works when I check the api route manually in my browser but does not upon sending an axios request. It also works when I'm deploying the backend on my local server but I imagine because they are both on the same domain.
Here is my express configuration on the backend.
const corsOptions = {
credentials: true,
origin: "http://localhost:3000",
optionsSuccessStatus: 200,
};
// Express backend for web application
const app = express();
app.set("trust proxy", true);
/////////////////////////////////////////////////////// Middleware //////////////////////////////////////////////////
app.use(cors(corsOptions));
app.use(cookieParser());
app.use(
session({
secret: "somethingsecretgoeshere",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: false,
secure: false,
maxAge: 10 * 60 * 100000,
sameSite: 'none'
},
})
);
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
passportConfig(passport);
app.use("/api", auth_routes);
app.use("/api", major_requirement_routes);
app.use("/api", user_course_routes);
export default app;
Here is the route at which I am making the get request to see if a user is authenticated
router.get("/auth/check", (req, res) => {
console.log(req.user)
console.log(req.cookies)
if (req.user) {
User.findOne({netId: req.user}, function (err, docs) {
if (err) {
console.log(err);
} else {
res.json({
auth: true,
user: req.user,
courseList: docs.courseList,
semesterList: docs.semesterList,
major: docs.major,
creditsApplied: docs.creditsApplied,
emailAddress: docs.emailAddress,
});
}
});
} else {
res.json({auth: false, id: null});
}
});
Here is my axios config
import axios from "axios";
const backend_url = "http://44.198.159.229:5000/api"
// const backend_url = "http://localhost:5000/api"
export default axios.create({
withCredentials: true,
baseURL: backend_url,
});
Here is my axios get request on the frontend
axios
.get("auth/check", { withCredentials: true,credentials: 'include',
})
.then(({ data}) => {
console.log(data)
if (data.auth) {
setIsAuthenticated(true);
setUser(data.user);
setCourseList(data.courseList);
setIsLoading(false);
} else {
setIsAuthenticated(false);
setCourseList(undefined);
setUser(undefined);
setIsLoading(false);
}
})
.catch(() =>
console.log(
"Something went wrong while trying to fetch your auth status."
)
);
}, []);
Okay so after a lot of research and playing around for a few days I have found a solution. I had to use a SSL and redirect traffic to an https server via AWS load balancing and set sameSite: None httpOnly: true, secure: true. I hope this helps someone else. This is because cookies can only be sent to cross origin sites that are secure. I also had to change my local host to run on https instead of http

Why is the express session cookie not stored in the browser's cookie storage on production but is working on localhost/development? (express + react)

I have created a test app, my react app is deployed at vercel and my node express is deployed at render.com. I set the same domain on both to solve cross-site cookie problems (app.mydomain.online)(api.mydomain.online). Now no error is showing when I view the cookie in the header but still when I check the cookie storage it is still not stored or not being saved at the browser's cookie storage.
server is created via npm init.
react is created via npm create-react-app.
as of now this is my sample code.
server
const express = require("express");
const cors = require("cors");
const session = require('express-session');
const app = express();
require('dotenv').config();
const PORT = process.env.PORT;
app.use(express.json());
app.use(cors({
origin: 'https://app.myDomain.online',
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true
}));
const oneDay = 1000 * 60 * 60 * 24;
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: 'none' }
}));
app.get('/createSession', (req, res) => {
req.session.user = 'user';
res.send('new session is created');
});
app.get('/', (req, res) => {
res.send('get sess')
});
app.get('/getSession', (req, res) => {
if(req.session.user){
res.send('active');
}else{
res.send('not active');
}
});
app.listen(PORT, () => {
console.log(`The server is running on port ${PORT}`);
});
react
import React from 'react'
import { useEffect } from 'react';
import Axios from 'axios';
function Test() {
useEffect(() => {
Axios.get(' https://api.myDomain.online/createSession',
{ withCredentials: true }
);
}, []);
return (
<div>Test</div>
)
}
export default Test;
From the documentation for express-session...
cookie.expires
Specifies the Date object to be the value for the Expires Set-Cookie attribute. By default, no expiration is set, and most clients will consider this a “non-persistent cookie” and will delete it on a condition like exiting a web browser application.
The docs go on to prefer the maxAge property to control this. Choose a time frame that makes sense for your application. For example, 1 week...
app.use(
session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: "none", maxAge: 7 * 24 * 60 * 60 * 1000 },
})
);
I have already fixed it a few days ago, I found out that the reason why the cookie was blocked was that the cookie has exactly the same domain as the server or probably has the same link address. As the domain of the server is api.myDomian.online, the cookie domain can't be api.myDomain.online. Not sure if that is the direct reason, but somewhat similar I think as the code works by setting a new domain to the cookie. I just removed the subdomain of the cookie like .myDomain.online and it works. here is my configuration. My backend is already deployed at aws when I test it but it could also work in render. I will try it out later on.
This is my new configuration
const oneDay = 1000 * 60 * 60 * 24;
const APP_SESSION = session({
secret: 'secrete',
resave: false,
saveUninitialized: true,
name: 'session',
cookie: {
secure: true,
sameSite: 'none'
maxAge: oneDay,
domain: '.domain.com'
}
});
Haven't tried to remove sameSite if it will still work.

How to make express-session work on a cross-site setup (Cannot set a cookie from the backend to the frontend)?

How to make express-session work on a cross-site setup (Cannot set a cookie from the backend to the frontend)?
I created a test app as I am having a hard time finding ways to set cookies to the front end. When I try to set sameSite: 'none' and cookie: true, I cannot find the cookie in the header. But when I set the cookie to false and remove the sameSite, The cookie was in the header but it was blocked and i am getting an error such as I should set the sameSite=none and secure to true. The website is deployed at render.com.
The link of the react looks like this
https://react-websiteName-2d0w.onrender.com
The link of the server looks like this
https://api-websiteName-hj7g.onrender.com
//server
const express = require("express");
const cors = require("cors");
const sessions = require('express-session');
const app = express();
const PORT = 3001;
app.use(express.json());
app.use(cors({
origin: 'https://react-websiteName-2d0w.onrender.com',
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true
}));
const oneDay = 1000 * 60 * 60 * 24;
app.use(sessions({
secret: "secret",
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true,
maxAge: oneDay,
sameSite:'none'
},
resave: false
}));
app.post('/createSession', (req, res) => {
req.session.user = req.body.user;
res.send('new session is created');
});
app.listen(PORT, () => {
console.log(`The server is running on port ${PORT}`);
});
//react app
import { useEffect } from 'react';
import Axios from 'axios';
import './App.css';
import axios from 'axios';
const App = () => {
axios.defaults.withCredentials = true;
useEffect(() => {
Axios.post('https://api-websiteName-hj7g.onrender.com/createSession',
{ user: "user" }
);
}, []);
return (
<div>
<h1>Check if there is a cookie</h1>
</div>
);
}
export default App;
I am trying to make express-session work on my project when deployed with different domian.
Are there better alternatives or methods that could work in render.com instead of express-session?

How to work with Express-session Cookies for authentication with front-end and back-end separated

I'm trying to figure out how sessions work for authentication purposes. I have an express server running on port 5000 with redis middleware creating the cookie on every response to the backend server. I also have a React app running on port 3000 to handle the front-end. The issue is that if someone goes to "localhost:3000", the cookie won't be created because they need to go to "localhost:5000" in order for a cookie to be created.
The only solution I can think of is to use useEffect() to make a request to the server to get the cookie every time. I'm not sure if this is the proper way to do it.
Server.ts:
import express from "express";
import redis from "redis";
import session from "express-session";
import connectRedis from "connect-redis";
import cors from "cors";
declare module "express-session" {
interface Session {
username: string;
password: string;
email: string;
}
}
const RedisStore = connectRedis(session);
const redisClient = redis.createClient();
redisClient.on("error", function (err) {
console.log("Could not establish a connection with redis. " + err);
});
redisClient.on("connect", function () {
console.log("Connected to redis successfully");
});
const app = express();
const corsOptions = {
origin: "http://localhost:3000",
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(
session({
name: "joshcookie",
store: new RedisStore({ client: redisClient, disableTouch: true }),
cookie: { maxAge: 1000 * 60 * 60 * 24, httpOnly: false, secure: false },
secret: "jklbflasjlkdbhfoub4ou",
saveUninitialized: true,
resave: false,
})
);
app.get("/", (req: express.Request, res: express.Response) => {
const session = req.session;
console.log(req.session.id);
if (session.username) {
res.send("user logged in");
} else {
res.send("user not logged in");
}
});
app.post("/register", (req, res) => {
console.log(req.body);
req.session.username = req.body.username;
res.end();
});
app.listen(5000, () => {
console.log("server listening on port 5000");
});
That's exactly what webpack devServer's proxy is for. If you're using webpack in your React application add to the webpack config something like:
devServer: {
proxy: {
'/api': 'http://localhost:5000',
},
},
And while in development all your requests to localhost:3000 will go out with cookies headers and will be redirected to your local node server under the hood.
Parcel has development proxy server as well since the version 2.0.

How to validate the integrity of session cookie on each page refresh, and should one?

I have a PassportJS authentication set up on my app with strategies for Facebook, Twitter, and Google, along with local. Here's what my authentication route currently looks like:
// /routes/auth-routes.js
import connectRedis from 'connect-redis';
import express from 'express';
import session from 'express-session';
import uuidv4 from 'uuid/v4';
import facebook from './auth-providers/facebook';
import google from './auth-providers/google';
import local from './auth-providers/local';
import twitter from './auth-providers/twitter';
const RedisStore = connectRedis(session);
const router = express.Router();
router.use(session({
name: process.env.SESSION_COOKIE,
genid: () => uuidv4(),
cookie: {
httpOnly: true,
sameSite: 'strict',
},
secret: process.env.SESSION_SECRET,
store: new RedisStore({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
ttl: 1 * 24 * 60 * 60, // In seconds
}),
saveUninitialized: false,
resave: false,
}));
// Social auth routes
router.use('/google', google);
router.use('/twitter', twitter);
router.use('/facebook', facebook);
router.use('/local', local);
// Logout
router.get('/logout', (req, res) => {
req.logout();
const cookieKeys = Object.keys(req.cookies);
if(cookieKeys.includes(process.env.USER_REMEMBER_COOKIE)) {
console.log('REMEMBER COOKIE EXISTS!');
const rememberCookie = process.env.USER_REMEMBER_COOKIE;
const sessionCookie = process.env.SESSION_COOKIE;
cookieKeys.forEach((cookie) => {
if(cookie !== rememberCookie && cookie !== sessionCookie) res.clearCookie(cookie);
});
res.redirect(req.query.callback);
} else {
console.log('NO REMEMBER');
req.session.destroy(() => {
cookieKeys.forEach((cookie) => {
res.clearCookie(cookie);
});
res.redirect(req.query.callback);
});
}
});
module.exports = router;
As apparent, I'm using Redis to store session cookies, which are then sent to the server along with all others cookies upon each page-reload. Here's my question:
Is this enough? Shouldn't I be validating the integrity of received session cookie by looking it up against the Redis store? But if I do that on every page load, won't that affect performance adversely? What's the standard way to handle this?
the repo is up at https://github.com/amitschandillia/proost/blob/master/web.
It look like you're missing part of the integration with PassportJS with express-session. Like the comment mentioned, express-session will store your session data in the redis store, and its key will be stored in the cookie sent to the user.
But you're logging in your users through passportJS, you need to connect these two middlewares together. From the passportJS docs :
http://www.passportjs.org/docs/configure/
Middleware
In a Connect or Express-based application, passport.initialize() middleware is required to initialize Passport. If your application uses persistent login sessions, passport.session() middleware must also be used.
app.configure(function() {
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});

Resources