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();
}
]
}
},
Related
I am new to node.js and I am trying to make a simple task list app using express.js and express-session. However, for a reason that I don't understand most of the routes are called two or tree times when I make a request and it shouldn't. For instance, if I send a request to /new the new task is sometimes added two (or three) times instead of one and this causes a problem...
I read in other threads that the problem could come from the browser trying to get a favicon, however if I log all incoming request url (console.log(req.url)) on the /new route, the duplicated requests are always /new and not a favicon...
Here is my code :
var express = require('express');
var session = require('express-session');
// Create a new express application instance
var app = express();
// Initialize session
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true,
cookie: {}
}));
// Initialize req.session.tasks if needed
app.use(function (req, res, next) {
if (req.session.tasks === undefined) {
req.session.tasks = [];
}
next();
});
app.get('/', function (req, res) {
res.send(req.session.tasks);
});
// Create a Test Task
app.get('/new', function (req, res) {
console.log(req.url);
req.session.tasks.push("Test Task");
res.redirect('/');
});
app.get('/clear', function (req, res) {
req.session.tasks = [];
res.redirect('/');
})
app.listen(3000, function () {
console.log('Task Server is listening on port 3000!');
});
Do you have a idea of what could be the cause of this problem and how to avoid it ??
Thanks a lot !
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.
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
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
I'm trying to make a chat application based on Node.js. I'd like to force websocket server (ws library) to using ExpressJS session system. Unfortunately, I've got stuck. MemoryStore hashes used to get sessions' data are different than session IDs in cookies. Could somebody explain me what I'm doing wrong?
Websocket server code part:
module.exports = function(server, clients, express, store) {
server.on('connection', function(websocket) {
var username;
function broadcast(msg, from) {...}
function handleMessage(msg) {...}
express.cookieParser()(websocket.upgradeReq, null, function(err) {
var sessionID = websocket.upgradeReq.cookies['sid'];
//I see same value in Firebug
console.log(sessionID);
//Shows all hashes in store
//They're shorter than sessionID! Why?
for(var i in store.sessions)
console.log(i);
store.get(sessionID, function(err, session) {
websocket.on('message', handleMessage);
//other code - won't be executed until sessionID in store
websocket.on('close', function() {...});
});
});
});
}
store object definition:
var store = new express.session.MemoryStore({
reapInterval: 60000 * 10
});
app configuration:
app.configure(function() {
app.use(express.static(app.get("staticPath")));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
store: store,
secret: "dO_ob",
key: "sid"
}));
});
Part of main code:
var app = express();
var httpServer = http.createServer(app);
var websocketServer = new websocket.Server({server: httpServer});
httpServer.listen(80);
Sample debugging output:
- websocket.upgradeReq.headers.cookie "sid=s%3A64a%2F6DZ4Mab8H5Q9MTKujmcw.U8PJJIR%2BOgONY57mZ1KtSPx6XSfcn%2FQPZ%2FfkGwELkmM"
- websocket.upgradeReq.cookies["sid"] "s:64a/6DZ4Mab8H5Q9MTKujmcw.U8PJJIR+OgONY57mZ1KtSPx6XSfcn/QPZ/fkGwELkmM"
- i "64a/6DZ4Mab8H5Q9MTKujmcw"
I found this works for me. Not sure it's the best way to do this though. First, initialize your express application:
// whatever your express app is using here...
var session = require("express-session");
var sessionParser = session({
store: session_store,
cookie: {secure: true, maxAge: null, httpOnly: true}
});
app.use(sessionParser);
Now, explicitly call the session middleware from the WS connection. If you're using the express-session module, the middleware will parse the cookies by itself. Otherwise, you might need to send it through your cookie-parsing middleware first.
If you're using the websocket module:
ws.on("request", function(req){
sessionParser(req.httpRequest, {}, function(){
console.log(req.httpRequest.session);
// do stuff with the session here
});
});
If you're using the ws module:
ws.on("connection", function(req){
sessionParser(req.upgradeReq, {}, function(){
console.log(req.upgradeReq.session);
// do stuff with the session here
});
});
For your convenience, here is a fully working example, using express, express-session, and ws:
var app = require('express')();
var server = require("http").createServer(app);
var sessionParser = require('express-session')({
secret:"secret",
resave: true,
saveUninitialized: true
});
app.use(sessionParser);
app.get("*", function(req, res, next) {
req.session.working = "yes!";
res.send("<script>var ws = new WebSocket('ws://localhost:3000');</script>");
});
var ws = new require("ws").Server({server: server});
ws.on("connection", function connection(req) {
sessionParser(req.upgradeReq, {}, function(){
console.log("New websocket connection:");
var sess = req.upgradeReq.session;
console.log("working = " + sess.working);
});
});
server.listen(3000);
I was able to get this working. I think you need to specify the secret on cookieParser instead of session store.
Example from my app:
var app = express();
var RedisStore = require('connect-redis')(express);
var sessionStore = new RedisStore();
var cookieParser = express.cookieParser('some secret');
app.use(cookieParser);
app.use(express.session({store: sessionStore}));
wss.on('connection', function(rawSocket) {
cookieParser(rawSocket.upgradeReq, null, function(err) {
var sessionID = rawSocket.upgradeReq.signedCookies['connect.sid'];
sessionStore.get(sessionID, function(err, sess) {
console.log(sess);
});
});
});
Feb 2022 update:
verifyClient is now discouraged. New methods of doing this is described in an issue comment.
Consult the example code for session parsing and verification for a full usage example. Sample of the verification function:
server.on('upgrade', function (request, socket, head) {
console.log('Parsing session from request...');
sessionParser(request, {}, () => {
if (!request.session.userId) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
console.log('Session is parsed!');
wss.handleUpgrade(request, socket, head, function (ws) {
wss.emit('connection', ws, request);
});
});
});
Original answer:
In version 3.2.0 of ws you have to do it a bit differently.
There is a full working example of express session parsing in the ws repo, specifically using a new feature verifyClient.
A very brief usage summary:
const sessionParser = session({
saveUninitialized: false,
secret: '$eCuRiTy',
resave: false
})
const server = http.createServer(app)
const wss = new WebSocket.Server({
verifyClient: (info, done) => {
console.log('Parsing session from request...')
sessionParser(info.req, {}, () => {
console.log('Session is parsed!')
done(info.req.session.userId)
})
},
server
})
wss.on('connection', (ws, req) => {
ws.on('message', (message) => {
console.log(`WS message ${message} from user ${req.session.userId}`)
})
})
WS v3.0.0 and above, has changed the behaviour so the given answers won't work out of the box for those versions. For current versions, the signature of the connection method is [function(socket, request)] and the socket no longer contains a reference to the request.
ws.on(
'connection',
function (socket, req)
{
sessionParser(
req,
{},
function()
{
console.log(req.session);
}
);
}
);
Currently, below is my workaround which is working fine. I just don't know it's disadvantages and security. I just prevent the server from listening if it doesn't have a session. (Share session from express-session to ws)
I haven't fully tested this though.
var http = require('http');
var express = require('express');
var expressSession = require('express-session');
var router = express.Router();
var app = express();
const server = http.createServer(app);
router.get('/', function(req, res, next) {
if(req.session.user_id) {
// Socket authenticated
server.listen(8080, function listening(){});
}
});