session.id changes and socket.io doesn't match - node.js

I'm running webpack-dev-server on localhost:8000 for my app and express+socket.io on port 3000 for my api. I've proxied requests to socket.io in webpack.config.js as such:
devServer: {
proxy: {
'/socket.io': {
target: 'http://localhost:3000',
ws: true
}
}
}
However, not only do the session ids in express and socket.io don't match, the session id in express changes every request:
Server:
let app = require('express')();
let session = require('express-session')({
secret: 'panopticon',
resave: true,
saveUninitialized: true
});
let server = require('http').createServer(app);
let io = require('socket.io')(server);
//session middleware
app.use(session);
io.use(require('express-socket.io-session')(session, {
autoSave: true
}));
let i=0;
app.get('/socket.io', (req, res) => {
console.log(i++, req.session.id);
//0 'ShgnU91kCZzC7xHP9B57ZtsCbwi3XjdB'
//1 'qLsYYpRZXpyoUrcKzF6K7uoAIKtE9oCh'
res.send();
});
io.on('connection', socket => {
console.log(socket.handshake.session.id);
//MRUYZMVstMh6ssNrq9LP-Z4vTaT5SZcs
});
Client:
//connect to socket
let socket = io();
//make two requests to /socket.io
fetch('socket.io').then(() => fetch('socket.io'));

The only way I got this to work was to do an AJAX request first to localhost:3000:
fetch('http://127.0.0.1:3000', {
credentials: 'include'
});
With the following handler on the response:
app.use('/', (req, res) => {
res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');
res.header('Access-Control-Allow-Credentials', 'true');
res.sendStatus(200);
});
GitHub Gist

Related

Am I doing anything wrong here node+express + vue SPA getCSRFTOKEN()

My index.js Server
// USE STRICT;
const express = require('express');
const app = express();
const session = require('express-session');
const http = require('http').Server(app);
const socket = require('socket.io');
const schedule = require('node-schedule');
const cors = require('cors');
const io = socket(http, {
cors: {
origin: 'http://localhost:8080',
methods: ['GET', 'POST'],
allowedHeaders: ['my-custom-header'],
credentials: true
}
});
const port = 8080;
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/uploads'));
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const mustacheExpress = require('mustache-express');
app.engine('html', mustacheExpress());
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
const secret = 'somesecretkeyhere';
const passport = require('passport');
const helmet = require('helmet');
const { sendMail } = require('./controllers/sellerAdsController');
// Gives us access to variables set in the .env file via `process.env.VARIABLE_NAME` syntax
// require('dotenv').config();
// Must first load the models before passport
require('./models/user');
// Pass the global passport object into the configuration function
require('./config/passport')(passport);
// This will initialize the passport object on every request
app.use(passport.initialize());
// Allows our remote applications to make HTTP requests to Express application
app.use(cors());
app.use(helmet());
app.use(express.urlencoded({ extended: false }));
// app.use(express.json()); //WARNING: Do not turn on. stops formidable for api calls
app.use(cookieParser(secret));
app.use(session({
secret: secret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true
}
}));
app.use(csrf());
// Stop page caching
app.use(function (req, res, next) {
res.set('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
next();
});
// Imports all of the routes from ./routes/index.js
app.use(require('./routes/api/v1'));
// Socket Operations
// io.on('connection', io => {
// let sessionId = io.id;
// io.on('clientHandshake', (data) => {
// console.log(data);
// io.emit('serverHandshake', { sessionId: sessionId });
// });
// });
// io.use((socket, next) => {
// const username = socket.handshake.auth.username;
// if (!username) {
// return next(new Error('invalid username'));
// }
// console.log(username);
// socket.username = username;
// next();
// });
io.on('connection', (socket) => {
console.log('👾 New socket connected! >>', socket.id);
// notify existing users
socket.broadcast.emit('user connected', {
userID: socket.id,
username: socket.username,
});
socket.on('private message', ({ content, to }) => {
socket.to(to).emit('private message', {
content,
from: socket.id,
});
console.log(content, to);
});
});
// EROOR HANDLING ROUTES MUST BE BENEATH ALL APP.USE AND ROUTES
// Check if request is from web or app (HTML/JSON)
// Handle 404
app.use(function (req, res) {
res.status(404);
res.render('404.html', { title: '404: File Not Found' });
});
// Handle 500
app.use(function (error, req, res) {
return res.send(error);
// res.status(500);
// res.render('500.html', { title: '500: Internal Server Error', error: error });
});
// SCHEDULED JOBS
const now = new Date();
let date = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0, 0);
schedule.scheduleJob(date, sendMail);
http.listen(port, () => {
console.log(`listening on *:${port}`);
});
And this is how I am getting from VUE
window.axios.get('/databank/getCSRF').then((response) => {
window.axios.defaults.headers.common['XSRF-TOKEN'] = response.data;
}, (err) => {
console.log(err)
})
And this is my login request header
XSRF-TOKEN from my login request header sent by axios
So Ive set my server up like that, and my vue SPA, but getCSRF() seems to be getting the request but I can't do a POST request back to the server throws an error
ForbiddenError: invalid csrf token
at csrf
Maybe because you wrote XSRF-TOKEN instead of CSRF-Token as it suggests in the Express Documentation.

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.

Node Express setting cookies

I may be misunderstanding here.
I have a node server running at localhost:3000, and a React app running at localhost:8080.
The React app is making a get request to the node server - my server code for this looks like:
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(cookieParser());
app.get('/', function (req, res) {
let user_token = req.cookies['house_user']; // always empty
if (user_token) {
// if the token exists, great!
} else {
crypto.randomBytes(24, function(err, buffer) {
let token = buffer.toString('hex');
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
res.cookie('house_user', token, {maxAge: 9000000000, httpOnly: true, secure: false });
res.send(token);
});
}
});
app.listen(3000, () => console.log('Example app listening on port 3000!'))
I'm trying to set the house_user token, so that I can later keep track of requests from users.
However, the token is not being set on the user (request from localhost:8080) - the house_user token is always empty (in fact, req.cookies is entirely empty). Do I need to do something else?
I just tried the code below (and it worked). As a reminder, you can just paste this in myNodeTest.js, then run node myNodeTest.js and visit http://localhost:3003. If it does work, then it probably means you're having CORS issues.
[EDIT] withCredentials:true should do the trick with axios.
axios.get('localhost:3000', {withCredentials: true}).then(function (res) { console.log(res) })
const express = require('express')
const cookieParser = require('cookie-parser')
const crypto = require('crypto');
const port = 3003
app.use(cookieParser());
app.get('/', function (req, res) {
let user_token = req.cookies['house_user']; // always empty
if (user_token) {
// if the token exists, great!
} else {
crypto.randomBytes(24, function(err, buffer) {
let token = buffer.toString('hex');
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
res.cookie('house_user', token, {maxAge: 9000000000, httpOnly: true, secure: true });
res.append('Set-Cookie', 'house_user=' + token + ';');
res.send(token);
});
}
});
app.get('/', (request, response) => {
response.send('Hello from Express!')
})
app.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
})
Making my comment into an answer since it seemed to have solved your problem.
Since you are running on http, not https, you need to remove the secure: true from the cookie as that will make the cookie only be sent over an https connection which will keep the browser from sending it back to you over your http connection.
Also, remove the res.append(...) as res.cookie() is all that is needed.

New Session ID when Using Isomorphic Fetch

I have an unexpected behavior when using isomorphic-fetch vs. request-promise related to Express sessions (and Express session ID in particular):
As part of the troubleshooting process, I implemented two methods in client.js for calling endpoints in server.js: 1) isomorphic-fetch, and 2) request-promise.
Client.js
// Method 1: isomorphic-fetch
require('es6-promise').polyfill();
require('isomorphic-fetch');
fetch('http://localhost:3000', {
credentials: 'same-origin',
})
.then(function(response) {
console.log(response.status);
});
fetch('http://localhost:3000', {
credentials: 'same-origin',
})
.then(function(response) {
console.log(response.status);
});
// Method 2: request-promise
var rp = require('request-promise').defaults({
jar: true
});
function requestPage() {
return rp('http://localhost:3000/');
}
requestPage()
.then(console.dir)
.then(requestPage)
.then(console.dir)
.catch(console.error);
Server.js
var app = require('express')();
app.use(require('morgan')('dev'));
var session = require('express-session');
var FileStore = require('session-file-store')(session);
app.use(session({
name: 'server-session-cookie-id',
secret: 'my express secret',
saveUninitialized: true,
resave: true,
store: new FileStore(),
cookie: {
secure: false
}
}));
app.get('/', function initViewsCount(req, res, next) {
console.log('req.session.id = ' + req.session.id);
if (typeof req.session.views === 'undefined') {
req.session.views = 1;
return res.end('Welcome to the file session demo. Refresh page!');
}
return next();
});
app.get('/', function incrementViewsCount(req, res, next) {
console.assert(typeof req.session.views === 'number',
'missing views count in the session', req.session);
req.session.views++;
return next();
})
app.use(function printSession(req, res, next) {
console.log('req.session', req.session);
return next();
});
app.get('/', function sendPageWithCounter(req, res) {
res.setHeader('Content-Type', 'text/html');
res.write('<p>views: ' + req.session.views + '</p>\n');
res.end();
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
When I execute node client.js, here is the output of the server:
req.session.id = deWKCvqcyGiAvVSUvHv2Db7sjvE7xN1E
req.session.id = MxLHWjbMMvV4GRfPSf6sQ12XvauiJJot
req.session.id = A3KTLMdBopQ7pAfcTsJhnzzdokdA7hGI
GET / 200 1.407 ms - -
GET / 200 7.625 ms - -
GET / 200 0.728 ms - -
req.session.id = A3KTLMdBopQ7pAfcTsJhnzzdokdA7hGI
req.session Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false },
views: 2,
__lastAccess: 1517449125197 }
GET / 200 6.902 ms - -
I confirmed that method 2 (request-promise) successfully persists the session on the server. In other words, session A3KTLMdBopQ7pAfcTsJhnzzdokdA7hGI is associated with this method.
However, as observed from the output, method 1 (isomorphic-fetch) generates two separate sessions on the server.
Question: Why does isomorphic-fetch create two separate sessions on the server?
Troubleshooting performed:
I replaced localhost with 127.0.0.1, but this did not change the behavior.
I replaced same-origin with include, but this did not change the behavior.
Environment:
node v6.10.3
isomorphic-fetch 2.2.1
request-promise 4.2.2

How do I set a session cookie via BrowserSync middleware?

I've recently moved from an Express web app to a BrowserSync app for nodejs. Using Express, if I wanted to set a cookie, my config would look something like this:
var express = require('express');
var session = require('express-session');
var finalhandler = require('finalhandler');
var serveStatic = require('serve-static'),
serve = serveStatic(__dirname);
var app = express();
app.use(session({ // see https://github.com/expressjs/session
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}))
.use(function(req, res, next) {
res.cookie('myCookie', 'my cookie value');
var done = finalhandler(req, res);
serve(req, res, done);
});
app.listen(8080);
My team has started using BrowserSync (via a gulp task), and my config, so far, looks something like this:
var gulp = require('gulp'),
browserSync = require('browser-sync'),
gulpLoadPlugins = require('gulp-load-plugins'),
plugins = gulpLoadPlugins();
gulp.task('browser-sync', function() {
browserSync({
server: {
baseDir: "./",
middleware: [
function(req, res, next) {
res.cookie('myCookie', 'my cookie value');
next();
}
]
},
port: 8080
});
});
However, the res object does not have a method named "cookie". Is there something similar to the session middleware for Expressjs that will work as BrowserSync middleware? Is there another way to set cookies in a BrowserSync server?
I had the same TypeError: res.cookie is not a function-error. The following configuration works for me.
browsersync: {
open: false,
port: 8080,
server: {
baseDir: distDir,
middleware: [
function(req, res, next) {
res.setHeader('set-cookie', 'name=value');
next();
}
]
}
},
The reason should be because res is a native response object from http https://nodejs.org/api/http.html#http_class_http_serverresponse
where cookie method is not available
Browser-sync uses connect, which has a slightly different API than express. This configuration worked for me to add a header and a cookie to my responses using browser-sync:
browsersync: {
open: false,
port: 8080,
server: {
baseDir: distDir,
middleware: [
function(req, res, next) {
res.setHeader('hello', 'world');
res.cookie('someCookie', 'cookieVal');
next();
}
]
}
},

Resources