Saving an express-session in socket.io - express

I can not understand why my sessions do not want to be saved.
Each time I connect, I get a new session id, but if I just go (through the browser) to tessocket.local, the session is saved and already when called through the socket, it is normally determined. That's actually the question - how to defeat it?
Forcing the session to be saved after io.on("connection" doesn't help.
If you remove transports: ["websocket"] - polling starts working, but then the session is not defined at all through the socket.
client.js
const socketOptions = {
"force new connection" : true,
"reconnectionAttempts": "Infinity",
"timeout" : 10000,
"transports" : ["websocket"]
};
// #ts-ignore
socket = io('https://tessocket.local',socketOptions)
// #ts-ignore
socket.onAny((event, ...args) => {
console.log(event, args);
});
return socket
server.js
const app = require('express')(),
http = require('http'),
https = require('https'),
// http = require("http"),
fs = require( "fs" ),
path = require("path"),
eSession = require("express-session"),
MongoDBStore = require('express-mongodb-session')(eSession),
store = new MongoDBStore({
uri: 'mongodb://admin:admin#localhost:27017/tmp?authSource=admin',
collection: 'sessions'
});
options = {
key: fs.readFileSync(path.resolve(__dirname, "../minica/test.local/key.pem")),
cert: fs.readFileSync(path.resolve(__dirname, "../minica/test.local/cert.pem")),
},
server = https.createServer(options, app)
const io = require("socket.io")(server, {
cors: "http://test.local:3000",
transports: ["websocket"]
}),
session = eSession({
secret: "my-secret",
resave: true,
saveUninitialized: true,
store,
cookie: {
httpOnly: false, // key
maxAge: null,
path: "/",
secure: true,
sameSite: 'none'
},
}),
sharedsession = require("express-socket.io-session");
// Attach session
app.use(session);
// Share session with io sockets
io.use(sharedsession(session));
io.on("connection", function(socket) {
// Accept a login event with user's data
console.log(socket.handshake.session.id)
socket.on("login", function(userdata) {
socket.handshake.session.userdata = userdata;
socket.handshake.session.save();
});
socket.on("logout", function(userdata) {
if (socket.handshake.session.userdata) {
delete socket.handshake.session.userdata;
socket.handshake.session.save();
}
});
});
server.listen(443);
socket.io#4.4.1
express#4

Related

Should I change the port from 3000 on an Express app / Nginx for security

Should I change the port from 3000 on an Express app and Nginx for security, or doesn't matter? I have it set to get the port number from aws parameter store, would this be more secure than storing it as an environmental variable on the server? If I should change the port what is a good number to change it to?
const param = require('./param');
param.getSecret('po').then((port) => {
app.listen(port, () => {
console.log("Express server running on: " + port)
})
});
param.js
const AWS = require("aws-sdk");
const ssm = new AWS.SSM({ region: "us-east-1" });
// const ssm = require('./aws-client');
const getSecret = async (ss) => {
console.log(`Getting secret for ${ss}`);
const params = {
Name: ss,
WithDecryption: true
};
const result = await ssm.getParameter(params).promise();
return result.Parameter.Value;
};
module.exports = { getSecret };
session
param.getSecret('ss').then((secret) => {
app.use(session({
proxy: true,
secret: secret,
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
console.log("SECRET: " + secret)
});

"New" redis session store with express, asynchronous creation?

I the past I could just easily add session management using redis as store in express using express-session like so:
import session from "express-session";
module.exports = (config?: {[p: string]: unknown}) => {
server.use(cookieParser());
// etc etc
server.use(session(sessCfg));
server.use('/', router);
return server;
}
This still work when creating a memory store/default store:
import session from "express-session";
import express = require('express');
const server = express();
function sessCfgGenerator() {
return {
"secret": "fdakjlhgafgdsahjkhldg",
"name": "bondinet.sid",
"cookie": {
"httpOnly": true,
"secure": false
},
"resave": false,
"saveUninitialized": true,
"unset": "destroy",
}
}
module.exports = (config?: {[p: string]: unknown}) => {
server.use(cookieParser());
// etc etc
server.use(session(sessCfgGenerator()));
server.use('/', router);
return server;
}
However nowadays to generate a redis store you have to create the client settings and then asynchronous connect to it.
let redisClient = redis.createClient({
socket: {
reconnectStrategy: function (retries: number) {
if (retries > 1000) {
// End reconnecting with built in error
return new Error("Retry time exhausted");
}
// reconnect after
return Math.min((retries + 1) * 100, 60000);
},
}
});
await redisClient.connect();
store = new RedisStore({
client: redisClient,
prefix: prefix,
});
I've tried to include the code into the above loader. To make sure it awaits for redis to start/connect:
import session from "express-session";
import express = require('express');
const server = express();
async generateRedisStore(prefix) {
let redisClient = redis.createClient({
socket: {
reconnectStrategy: function (retries: number) {
if (retries > 1000) {
// End reconnecting with built in error
return new Error("Retry time exhausted");
}
// reconnect after
return Math.min((retries + 1) * 100, 60000);
},
}
});
await redisClient.connect();
return new RedisStore({
client: redisClient,
prefix: prefix,
});
}
async function sessCfgGenerator() {
const opt = {
"secret": "fdakjlhgafgdsahjkhldg",
"name": "bondinet.sid",
"cookie": {
"httpOnly": true,
"secure": false
},
"resave": false,
"saveUninitialized": true,
"unset": "destroy",
store: await generateRedisStore('');
}
}
module.exports = (config?: {[p: string]: unknown}) => {
server.use(cookieParser());
// etc etc
sessCfgGenerator.then(cfg => {
server.use(session(cfg));
console.log("SESSION STARTED");
});
server.use('/', router);
return server;
}
However when I use the asynchronous version I notice that the session management is actually never "available". Even though the line runs (I see clearly the "SESSION STARTED" in the logs), whenever I test a route the session isn't there (below shows "undefined"):
router.get('/test', function(req, res, next) {
const r = 'respond with a test - ' + (req.session ? "loaded" : "undefined");
res.send(r);
});
What is the solution, and how can I make this work? These are actively used libraries so it must be working right?

res.cookie not working with apollo-server-express

I've been trying to send a cookie back to the client from the server. I get the response data but i don't see "set-cookie" in the response headers
My Apollo Server Configuration:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, connection, res }) => ({
dummyModels: dummyModels,
models: models,
req,
connection,
res,
currentUser: dummyModels.users[2],
dummyUsers: dummyModels.dummyUsers,
}),
});
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
// preflightContinue: true,
}));
My resolver:
login: async (parent, args, context) => {
const _include_headers = function(body, response, resolveWithFullResponse) {
return {'headers': response.headers, 'data': body};
};
const loginRequestOptions = {
method: 'POST',
uri: 'http://localhost:3000/incorta/authservice/login',
qs: {
// access_token: 'xxxxx xxxxx', // -> uri + '?access_token=xxxxx%20xxxxx'
user: args.input.username,
pass: args.input.password,
tenant: args.input.tenantName,
},
transform: _include_headers,
json: true // Automatically parses the JSON string in the response
};
const loginResponse = await request(loginRequestOptions);
console.log(loginResponse);
context.res.cookie(
'JSESSIONID',
tough.Cookie.parse(loginResponse.headers['set-cookie'][0]).value,
{
// expires : new Date(Date.now() + 9999999),
// path: '/incorta/',
// HttpOnly: false,
// maxAge: 1000 * 60 * 60 * 24 * 99, // 99 days
},
);
context.res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
return loginResponse.data;
},
Note: i'm using request-promise-native to make the request
My Apollo Client Configuration:
const httpLink = createHttpLink({
uri: 'http://172.16.16.130:4000/graphql',
credentials: 'include',
fetchOptions: {
credentials: 'include',
},
});
const wsLink = new WebSocketLink({
uri: 'ws://172.16.16.130:4000/graphql',
options: {
reconnect: true,
connectionParams: {
headers: {
'x-user-header': localStorage.getItem('userObject'),
},
},
}
});
const terminatingLink = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const link = ApolloLink.from([terminatingLink]);
const cache = new InMemoryCache();
export const client = new ApolloClient({
link,
cache,
});
I have tried tinkering with options. i don't know what i'm missing here.
You can use the apollo-server-plugin-http-headers package for setting cookies in apollo server.
Usage is as simple as this from within your resolvers:
context.setCookies.push({
name: "cookieName",
value: "cookieContent",
options: {
domain: "example.com",
expires: new Date("2021-01-01T00:00:00"),
httpOnly: true,
maxAge: 3600,
path: "/",
sameSite: true,
secure: true
}
});

Express session id not saving in cookie (graphql)

I cannot for the life of this program figure out why this cookie isn't being saved in the browser. If I run the graphql playground (which is on port 4000, the same as the server), the session ID will be stored in the browser no problem. I have cors enabled so this shouldn't be blocking this. But if I send the login request from any other url, it won't save it in the browser. I have tried multiple browsers and it won't save on any of them and I've tried logging the sessions to make sure I am actually saving them. Any ideas?
const { GraphQLServer } = require('graphql-yoga');
const session = require('express-session');
const bcrypt = require('bcryptjs');
const ms = require('ms');
const typeDefs = `
type Query {
isLoggedIn: Boolean!
}
type Mutation {
logIn(email: String!, password: String!): Boolean!
signUp(email: String!, password: String!): Boolean!
}
`
// mock mockDBbase
const mockDB = {};
const resolvers = {
Query: {
// is the user authenticated
isLoggedIn: (parent, args, ctx) => {
return ctx.session.isLoggedIn === true;
}
},
Mutation: {
// user can sign up for a new account
signUp: async (parent, { email, password }, ctx) => {
// if user is already in the DB
if (mockDB[email]) {
throw new Error('This user already exists, please log in.');
}
const saltRounds = 14; // roughly 1.5 secs on 2GHZ CPU
// store password in mock DB (replace with real DB)
mockDB[email] = {
// salt and hash pw
password: await bcrypt.hashSync(password, saltRounds),
};
return true;
},
// authenticates user into respective account
logIn: async (parent, { email, password }, ctx) => {
// grab user from DB
const user = mockDB[email];
if (user) {
// make sure pw matches
if (await bcrypt.compareSync(password, user.password)) {
// set user logged in flag
ctx.session.isLoggedIn = true;
return true;
}
throw new Error('User email or password is incorrect.');
}
throw new Error('User email or password is incorrect.');
}
}
}
// opts
const opts = {
port: 4000,
cors: {
credentials: true,
origin: "*"
}
};
// context
const context = req => ({
session: req.request.session,
});
// server
const server = new GraphQLServer({
typeDefs,
resolvers,
context,
});
const SESSION_SECRET = 'my-super-secret-secret';
server.express.set('trust proxy', 1) // trust first proxy
// session middleware
server.express.use(
session({
name: 'SSID',
// change this to randomly generate a secret
secret: SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: process.env.NODE_ENV === 'production',
maxAge: ms('1d'),
}
})
);
// start server
server.start(opts, () => console.log(`Server is running on http://localhost:${opts.port}`));

express-session not saving data

I have a simple MEAN app and I want to implement a simple "home-made" user authentication. My idea is to save the userId in the session when he logs in, and to check if userId exists in the session on each page request (for example, when getting the list of all users).
Backend - server.js:
const express = require("express");
const session = require("express-session");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
var MemoryStore = session.MemoryStore;
app.use(
session({
name: "app.sid",
secret: "my_s3cr3t",
resave: true,
store: new MemoryStore(),
saveUninitialized: true
})
);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());
const dbConfig = require("./config/database.config.js");
const mongoose = require("mongoose");
mongoose.Promise = global.Promise;
mongoose
.connect(dbConfig.url)
.then(() => {
// ...
})
.catch(err => {
// ...
process.exit();
});
require("./app/routes/user.routes.js")(app);
require("./app/routes/task.routes.js")(app);
require("./app/routes/login.routes.js")(app);
app.listen(3333, () => {
console.log("Server is listening on port 3333");
});
When a user clicks the Login button, a method from the frontend controller is called:
Frontend - login.controller.js:
vm.login = function() {
userService.getUserByUsername(vm.username).then(user => {
if (user.password === vm.password) {
console.log("Login ok");
loginService.login(user).then(($window.location.href = "/#!main"));
} else {
console.log("Login not ok");
}
});
};
Backend - login.controller.js:
exports.login = (req, res) => {
req.session.userId = req.body._id;
req.session.save(function(err) {
console.log(err); // prints out "undefined", so there's no error
});
console.log(req.session);
res.status(200).send({
message: "Login ok"
});
};
The frontend LoginController prints out "Login ok" (assuming that I entered correct credentials) and redirects me to the "main" page which uses main.controller.js:
In the meantime, the backend login controller prints out the following:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
userId: '5b4746cafe30b423181ad359' }
So there is definitely a userId in the session content. However, when I get redirected to the main.html and the main.controller.js gets invoked, it calls:
loginService.getSession().then(data => console.log(data));
(I just want to check if the userId is still in the session, and later I will perform some useful actions)
The getSession() method in the frontend LoginService only does the $http call:
function getSession() {
return $http.get("http://localhost:3333/session").then(
function(response) {
return response.data;
},
function(error) {
console.log(error.status);
}
);
}
This one calls the method which is defined in the backend LoginController:
exports.getSession = (req, res) => {
console.log(req.session);
if (req.session.userId) {
res
.status(200)
.send({ message: "Session existing with userId " + req.session.userId });
} else {
res.status(404).send({ message: "Session not existing" });
}
};
The frontend call prints the status code 404 in the console, while in the backend I get the following output:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
(no userId is present...)
One more thing... In a few tutorials I saw that they are using cookie-parser. However, when I try to use it, I don't get any data from my database, only the static text is displayed. So I removed it temporarily from server.js.
EDIT:
I tried adding MongoStore to my app:
const MongoStore = require("connect-mongo")(session);
...
app.use(
session({
name: "app.sid",
secret: "G4m1F1c4T10n_#ppL1c4t10N",
resave: true,
saveUninitialized: false,
cookie: { maxAge: 600000 },
store: new MongoStore({ url: "mongodb://localhost:27017/myAppDb" })
})
);
...but nothing changed.
How can I get my sessions to work?
As I found out after talking to several people, sessions are more or less deprecated and the new way of handling these things are the tokens. So I switched to JWT and it's working great.