Save the session directly to database bypassing the session store? - node.js

I'm using SQL Server as database and I couldn't get the session store npm package listed for SQL Server to work. Are there any concerns with inserting the sessionID into the database directly and then verifying what the client sends as sessionID matches the database sessionID? Do I need to tell it somehow to not save the session in memory?
const uuidv1 = require('uuid/v1');
const bcrypt = require('bcrypt');
app.use(session({
genid: uuidv1,
name: 'session',
secret: 'thesecretcode',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, /*no https*/
httpOnly: true,
sameSite: true,
maxAge: 600
}
}));
app.all('*', requireAuthentication);
function requireAuthentication(req, res, next) {
console.log(req.sessionID);
if (req.session.user) {
req.session.views++;
console.log(req.session.user)
res.render('home'); /*when logged in*/
} else {
req.session.views = 1;
req.session.user = 'MrUser';
next();
}
}
app.get('/', (req, res) => {
const saltRounds = 10;
bcrypt.hash(req.sessionID, saltRounds, function(err, hash) {
let promise = insert(req.session.user, hash);
});
res.render('landing'); /*when not logged in*/
})
function insert(theuser, sessionID) {
var thequery = "insert into session (sessiondata,sessionid,lasttouchedutc) values(#theuser,#sessionID,getdate())";
return new Promise(function(resolve, reject) {
var con = new msSqlConnecter.msSqlConnecter(config);
con.connect().then(function() {
new con.Request(thequery)
.addParam("theuser", TYPES.VarChar, theuser)
.addParam("sessionID", TYPES.VarChar, sessionID)
.onComplate(function(count, datas) {
resolve(datas);
// res.end()
}).onError(function(err) {
console.log(err);
}).Run();
}).catch(function(ex) {
console.log(ex);
});
});
}

It's more performant to use Redis but what you're doing is fine. Sessions have always been managed by matching a server-side cookie value to what is typically stored in an RDBMS.
Summary:
What you're doing is fine.
Use Redis to management sessions as it is much faster than hitting SQL server or any database

Related

axios is not sending the session ID with the request from vue at localhost:5173 to backend at localhost:5000

I am trying to send an axios request from the vue front end to node js backend with sessionID to handle sessions for admins.
this is a login sample to login admins
axios.defaults.withCredentials = true;
const submit = async (data) => {
const { email, password } = data;
const url = "http://localhost:5000/api/v1/users/loginAdmin";
try {
const res = await axios.post(url, {
email,
password,
});
error.value = undefined;
console.log(res.data);
const loginIng = useLogin();
loginIng.logIn(res.data);
router.push("/");
} catch (err) {
console.log(err);
error.value = err.response.data.message;
}
};
after the login is successfully done and that what happened with me, the user is pushed to the root at / and beforeMount the component, there a check to validate the admin credintials in the backend, like this
axios.defaults.withCredentials = true;
setup() {
const login = useLogin();
const router = useRouter();
onBeforeMount(() => {
axios
.post("http://localhost:5000/api/v1/users/validateAdmin")
.then((res) => {
login.logIn(res.data);
})
.catch((err) => {
console.log(err);
router.push("/login");
});
});
return {};
},
In the backend in the app.js, there is a session and cors policy to allow origin from the front end, and session is stored in sqlite3 like the following.
app.use(
session({
store: new KnexSessionStore({
knex: db,
tablename: "sessions",
}),
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24,
secure: process.env.NODE_ENV === "production",
},
})
);
in the login admin the session is saving a user with his credintials
like this
const loginAdmin = async (req, res) => {
req.session.user = admin;
}
and this is the ValidateAdmin endpoint sample
const validateAdmin = async (req, res) => {
const userSession = req.session;
const user = userSession.user;
}
the data is being saved to the sqlite3 file in the table, but each time the user visits the endpoint, there is a new id generated for him while i do not need that to happen
What I have already tried:
setting rolling: false, in the session
adding
app.use((req, res, next) => {
req.session.touch();
next();
});
to the app.js
Thanks in advance

req.session.user get undefined whenever i change anything on my server, and on the first i make a call from the frontend, then works well

my code works
but whenever i made a change in the server.js, the req.session.user turns undefined.
This also happens the first time i make a request from the front, i do get authenticate. but right after i dont get the user.name and cookie cause it reads undefined.
if I run it again, it works and it keeps working until i quit the server and start again. it repeats the same pattern, doesnt work first time, then it works.
i believe is related to the backend as any change in the backend gets session.user undefined,
server.js
const express = require("express");
const session = require("express-session");
const app = express();
const mysql = require("mysql2/promise");
app.use(express.json());
app.use(
session({
key: "userId",
secret: "password",
resave: false,
saveUninitialized: false,
cookie: {
expires: 1000 * 60 * 60 * 24,
},
})
);
app.use(
cors({
credentials: true,
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
})
);
app.post("/login", async (req, res) => {
const { userName, password } = req.body;
try {
const [result] = await db.query(
`select * from user
where
user = ? and
password = ? `,
[userName, password]
);
if (result.length > 0) {
req.session.user = {
loggedIn: true,
user_id: result[0].user_id,
name: result[0].name,
};
res.send(req.session.user);
} else {
res.send({ message: "Wrong password or user" });
}
} catch (error) {
console.error(error);
}
});
// checking whats on the session
app.use((req, res, next) => {
console.log(req.session.user);
next();
});
// session retriving
app.get("/login", (req, res) => {
req.session.user
? res.send({
loggedIn: true,
name: req.session.user.name,
user_id: req.session.user.user_id,
})
: res.send({ loggedIn: false });
});
app.js
Axios.defaults.withCredentials = true;
useEffect(() => {
Axios.get("http://localhost:3001/login").then(({ data }) => {
setUser({
loggedIn: data.loggedIn,
name: data.name,
user_id: data.user_id,
});
console.log(data);
});
}, []);
Whenever you change server.js - your server gets restarted, and since you don't have a session store where your session is saved the session is lost everytime the server is restarted. (the session is kept only in memory)
When you check out session readme, https://github.com/expressjs/session it says:
Warning The default server-side session storage, MemoryStore, is
purposely not designed for a production environment. It will leak
memory under most conditions, does not scale past a single process,
and is meant for debugging and developing. For a list of stores, see compatible session stores.
So check out: https://github.com/expressjs/session#compatible-session-stores to find a store that might suit you.
Since session are stored on memory it will reset everytime your backend got restarted. If you don't want that you can create one static session/token to use on DEVELOPMENT ONLY.

Session data still not persistent between routes, even though session management is done via connect-mongo in MongoDB

Even though I let MongoStore handle my sessions, my sessions are still undefined when I try to access sessions from a different api route.
For example in my /login route:
app.route("/login")
.post(async (req, res) => {
var username = req.body.username,
password = req.body.password;
console.log('\t#/login');
try {
var user = await User.findOne({ username: username }).exec();
if (!user) {
res.redirect("/login");
}
user.comparePassword(password, (error, match) => {
if (!match) {
console.log('redirecting to /login')
res.redirect("/login");
}
});
req.session.user_sid = {user};
console.log('\treq.session:');
console.log(req.session) // output screenshot provided below
} catch (error) {
console.log(error)
}
});
And it outputs as:
However, when I try to access the req.session from my /dashboard route, I get undefined. The /login route and its corresponding log is below:
app.get("/dashboard", (req, res) => {
console.log("\t#/dashboard route:")
console.log("\treq.session:");
console.log(req.session); // session is undefined even though it wasnt in my login route
if (req.session.user && req.cookies.user_sid) {
// res.sendFile(__dirname + "/public/dashboard.html");
console.log(req.session);
res.send("send something")
} else {
res.send("go back to /login");
}
});
I let mongo manage my sessions like:
const MongoStore = require("connect-mongo");
app.use(session({
secret: 'mysecret',
resave: false,
store: MongoStore.create({
mongoUrl: sessionDbUrl,
collection: 'sessions',
ttl : 14 * 24 * 60 * 60, // 14 days
}),
// store: new MongoStore({ mongooseConnection: db }),
saveUninitialized: true,
cookie: { maxAge: 1000 * 60 * 60 * 24, secure: true }, // 24 hours
}));
My problem is that the session do not persist within my routes, even though I tried Mongo. When I let mongo handle my sessions, does these sessions get stored in Mongo that I can view in Compass? My understanding is that, session management done via mongodb simply makes sessions data persistent throughout the app without actually needing to store the sessions as collections in the database. Please correct my understanding if I'm wrong, I've been stuck on this for days. TIA

Node js express Session get expired when change in code

Hi i am doing login application using node js (express framework) and mysql, for this using express-session. my session getting expires soon and also when there is change in code. I am using nodmon for automatic server response. I have attached my code here,
var session = require('express-session');
app.use(session({
secret: 'page builder',
resave: true,
saveUninitialized: false,
cookie: {secure: false, expires: new Date(Date.now() +
config.sessionTime)}
}));
setting session after user login:
app.post('/loginuser', function (req, res){
var datajsonEle = req.body;
con.query('SELECT * FROM users where userId=?', datajsonEle.userID, function (error, results, fields){
if (error){
throw error;
}else {
if(results.length > 0){
if(results[0].password === datajsonEle.userPassword){
sess = req.session;
sess.userInfo = results[0];
req.session.save();
res.end("success");
}else {
res.end("password");
}
}else {
res.end("userId");
}
// res.end(JSON.stringify(results));
}
});
});
When redirect to home.
var sess;
app.get('/', function (req, res, next){
sess = req.session;
console.log(sess);
if(sess.userInfo !== undefined){
console.log(sess.userInfo.initStage);
if(sess.userInfo.initStage === 0){
res.render('index', { title: sess.userInfo });
}else {
res.redirect('/home');
}
}else {
res.redirect('/login');
}
});
For every time when i reload after 5 min or changed my code and reload session getting empty. Please help me i am new to node js
The default store for express-session is the MemoryStore, which disappears when express restarts!
You need to use the store variable when initializing express-session to set the initialized store. The package has a list of options. Most require a persistent database somewhere (MongoDB, redis, memcache), but there is session-file-store if you're just trying things out locally.

How to share sessions with Socket.IO 1.x and Express 4.x?

How can I share a session with Socket.io 1.0 and Express 4.x? I use a Redis Store, but I believe it should not matter. I know I have to use a middleware to look at cookies and fetch session, but don't know how. I searched but could not find any working
var RedisStore = connectRedis(expressSession);
var session = expressSession({
store: new RedisStore({
client: redisClient
}),
secret: mysecret,
saveUninitialized: true,
resave: true
});
app.use(session);
io.use(function(socket, next) {
var handshake = socket.handshake;
if (handshake.headers.cookie) {
var str = handshake.headers.cookie;
next();
} else {
next(new Error('Missing Cookies'));
}
});
The solution is surprisingly simple. It's just not very well documented. It is possible to use the express session middleware as a Socket.IO middleware too with a small adapter like this:
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
Here's a full example with express 4.x, Socket.IO 1.x and Redis:
var express = require("express");
var Server = require("http").Server;
var session = require("express-session");
var RedisStore = require("connect-redis")(session);
var app = express();
var server = Server(app);
var sio = require("socket.io")(server);
var sessionMiddleware = session({
store: new RedisStore({}), // XXX redis server config
secret: "keyboard cat",
});
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res || {}, next);
});
app.use(sessionMiddleware);
app.get("/", function(req, res){
req.session // Session object in a normal request
});
sio.sockets.on("connection", function(socket) {
socket.request.session // Now it's available from Socket.IO sockets too! Win!
});
server.listen(8080);
Just a month and a half ago I dealt with the same problem and afterwards wrote an extensive blog post on this topic which goes together with a fully working demo app hosted on GitHub. The solution relies upon express-session, cookie-parser and connect-redis node modules to tie everything up. It allows you to access and modify sessions from both the REST and Sockets context which is quite useful.
The two crucial parts are middleware setup:
app.use(cookieParser(config.sessionSecret));
app.use(session({
store: redisStore,
key: config.sessionCookieKey,
secret: config.sessionSecret,
resave: true,
saveUninitialized: true
}));
...and SocketIO server setup:
ioServer.use(function (socket, next) {
var parseCookie = cookieParser(config.sessionSecret);
var handshake = socket.request;
parseCookie(handshake, null, function (err, data) {
sessionService.get(handshake, function (err, session) {
if (err)
next(new Error(err.message));
if (!session)
next(new Error("Not authorized"));
handshake.session = session;
next();
});
});
});
They go together with a simple sessionService module I made which allows you to do some basic operations with sessions and that code looks like this:
var config = require('../config');
var redisClient = null;
var redisStore = null;
var self = module.exports = {
initializeRedis: function (client, store) {
redisClient = client;
redisStore = store;
},
getSessionId: function (handshake) {
return handshake.signedCookies[config.sessionCookieKey];
},
get: function (handshake, callback) {
var sessionId = self.getSessionId(handshake);
self.getSessionBySessionID(sessionId, function (err, session) {
if (err) callback(err);
if (callback != undefined)
callback(null, session);
});
},
getSessionBySessionID: function (sessionId, callback) {
redisStore.load(sessionId, function (err, session) {
if (err) callback(err);
if (callback != undefined)
callback(null, session);
});
},
getUserName: function (handshake, callback) {
self.get(handshake, function (err, session) {
if (err) callback(err);
if (session)
callback(null, session.userName);
else
callback(null);
});
},
updateSession: function (session, callback) {
try {
session.reload(function () {
session.touch().save();
callback(null, session);
});
}
catch (err) {
callback(err);
}
},
setSessionProperty: function (session, propertyName, propertyValue, callback) {
session[propertyName] = propertyValue;
self.updateSession(session, callback);
}
};
Since there is more code to the whole thing than this (like initializing modules, working with sockets and REST calls on both the client and the server side), I won't be pasting all the code here, you can view it on the GitHub and you can do whatever you want with it.
express-socket.io-session
is a ready-made solution for your problem. Normally the session created at socket.io end has different sid than the ones created in express.js
Before knowing that fact, when I was working through it to find the solution, I found something a bit weird. The sessions created from express.js instance were accessible at the socket.io end, but the same was not possible for the opposite. And soon I came to know that I have to work my way through managing sid to resolve that problem. But, there was already a package written to tackle such issue. It's well documented and gets the job done. Hope it helps
Using Bradley Lederholz's answer, this is how I made it work for myself. Please refer to Bradley Lederholz's answer, for more explanation.
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io');
var cookieParse = require('cookie-parser')();
var passport = require('passport');
var passportInit = passport.initialize();
var passportSession = passport.session();
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var sessionMiddleware = session({
secret: 'some secret',
key: 'express.sid',
resave: true,
httpOnly: true,
secure: true,
ephemeral: true,
saveUninitialized: true,
cookie: {},
store:new mongoStore({
mongooseConnection: mongoose.connection,
db: 'mydb'
});
});
app.use(sessionMiddleware);
io = io(server);
io.use(function(socket, next){
socket.client.request.originalUrl = socket.client.request.url;
cookieParse(socket.client.request, socket.client.request.res, next);
});
io.use(function(socket, next){
socket.client.request.originalUrl = socket.client.request.url;
sessionMiddleware(socket.client.request, socket.client.request.res, next);
});
io.use(function(socket, next){
passportInit(socket.client.request, socket.client.request.res, next);
});
io.use(function(socket, next){
passportSession(socket.client.request, socket.client.request.res, next);
});
io.on('connection', function(socket){
...
});
...
server.listen(8000);
Working Example for PostgreSQL & Solving the problem of getting "an object with empty session info and only cookies":
Server-Side (Node.js + PostgreSQL):
const express = require("express");
const Server = require("http").Server;
const session = require("express-session");
const pg = require('pg');
const expressSession = require('express-session');
const pgSession = require('connect-pg-simple')(expressSession);
const PORT = process.env.PORT || 5000;
const pgPool = new pg.Pool({
user : 'user',
password : 'pass',
database : 'DB',
host : '127.0.0.1',
connectionTimeoutMillis : 5000,
idleTimeoutMillis : 30000
});
const app = express();
var ioServer = require('http').createServer(app);
var io = require('socket.io')(ioServer);
var sessionMiddleware = session({
store: new RedisStore({}), // XXX redis server config
secret: "keyboard cat",
});
io.use(function(socket, next) {
session(socket.request, {}, next);
});
app.use(session);
io.on("connection", socket => {
const ioSession = socket.request.session;
socket.on('userJoined', (data) => {
console.log('---ioSession---', ioSession)
}
}
Client-Side (react-native app):
To solve the problem of getting "empty session object" you need to add withCredentials: true
this.socket = io(`http://${ip}:5000`, {
withCredentials: true,
});
I have kinda solved it, but it is not perfect. Does not support signed cookies etc. I used express-session 's getcookie function. The modified function is as follows:
io.use(function(socket, next) {
var cookie = require("cookie");
var signature = require('cookie-signature');
var debug = function() {};
var deprecate = function() {};
function getcookie(req, name, secret) {
var header = req.headers.cookie;
var raw;
var val;
// read from cookie header
if (header) {
var cookies = cookie.parse(header);
raw = cookies[name];
if (raw) {
if (raw.substr(0, 2) === 's:') {
val = signature.unsign(raw.slice(2), secret);
if (val === false) {
debug('cookie signature invalid');
val = undefined;
}
} else {
debug('cookie unsigned')
}
}
}
// back-compat read from cookieParser() signedCookies data
if (!val && req.signedCookies) {
val = req.signedCookies[name];
if (val) {
deprecate('cookie should be available in req.headers.cookie');
}
}
// back-compat read from cookieParser() cookies data
if (!val && req.cookies) {
raw = req.cookies[name];
if (raw) {
if (raw.substr(0, 2) === 's:') {
val = signature.unsign(raw.slice(2), secret);
if (val) {
deprecate('cookie should be available in req.headers.cookie');
}
if (val === false) {
debug('cookie signature invalid');
val = undefined;
}
} else {
debug('cookie unsigned')
}
}
}
return val;
}
var handshake = socket.handshake;
if (handshake.headers.cookie) {
var req = {};
req.headers = {};
req.headers.cookie = handshake.headers.cookie;
var sessionId = getcookie(req, "connect.sid", mysecret);
console.log(sessionId);
myStore.get(sessionId, function(err, sess) {
console.log(err);
console.log(sess);
if (!sess) {
next(new Error("No session"));
} else {
console.log(sess);
socket.session = sess;
next();
}
});
} else {
next(new Error("Not even a cookie found"));
}
});
// Session backend config
var RedisStore = connectRedis(expressSession);
var myStore = new RedisStore({
client: redisClient
});
var session = expressSession({
store: myStore,
secret: mysecret,
saveUninitialized: true,
resave: true
});
app.use(session);
Now, the original accepted answer doesn't work for me either. Same as #Rahil051, I used express-socket.io-session module, and it still works. This module uses cookie-parser, to parse session id before entering express-session middleware.
I think it's silmiar to #pootzko, #Mustafa and #Kosar's answer.
I'm using these modules:
"dependencies":
{
"debug": "^2.6.1",
"express": "^4.14.1",
"express-session": "^1.15.1",
"express-socket.io-session": "^1.3.2
"socket.io": "^1.7.3"
}
check out the data in socket.handshake:
const debug = require('debug')('ws');
const sharedsession = require('express-socket.io-session');
module.exports = (server, session) => {
const io = require('socket.io').listen(server);
let connections = [];
io.use(sharedsession(session, {
autoSave: true,
}));
io.use(function (socket, next) {
debug('check handshake %s', JSON.stringify(socket.handshake, null, 2));
debug('check headers %s', JSON.stringify(socket.request.headers));
debug('check socket.id %s', JSON.stringify(socket.id));
next();
});
io.sockets.on('connection', (socket) => {
connections.push(socket);
});
};

Resources