I'm building web app using Node.js Express.js for the server-side and Angular 6 SPA for the client.
Using the simple Express.js code, below, I've successfully authenticated a user via SAML2.js ADFS and now I want to access the user on the client side Angular SPA. How do I do that?
I found a similar setup here, but there is not an answer there and its a bit dated.
var saml2 = require('saml2-js');
var fs = require('fs');
var express = require('express');
var https = require('https');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({
extended: true
}));
// Create service provider
var sp_options = {
entity_id: "https://localhost:44301/",
private_key: fs.readFileSync("key.pem").toString(),
certificate: fs.readFileSync("certificate.crt").toString(),
assert_endpoint: "https://localhost:44301/assert",
force_authn: true,
auth_context: { comparison: "minimum", class_refs: ["urn:oasis:names:tc:SAML:2.0:ac:classes:password"] },
nameid_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
sign_get_request: false,
allow_unencrypted_assertion: true
};
var sp = new saml2.ServiceProvider(sp_options);
// Create identity provider
var idp_options = {
sso_login_url: "https://mmusmaadfs.company.com/adfs/ls/",
sso_logout_url: "https://mmusmaadfs.company.com/adfs/ls/",
certificates: [fs.readFileSync("./2018ADFSSigningBase64Cert.cer").toString()],
force_authn: true,
sign_get_request: false,
allow_unencrypted_assertion: true
};
var idp = new saml2.IdentityProvider(idp_options);
// ------ Define express endpoints ------
// Endpoint to retrieve metadata
app.get("/metadata.xml", function(req, res) {
res.type('application/xml');
res.send(sp.create_metadata());
});
// Starting point for login
app.get("/login", function(req, res) {
sp.create_login_request_url(idp, {}, function(err, login_url, request_id) {
if (err != null)
return res.send(500);
res.redirect(login_url);
});
});
// Assert endpoint for when login completes
app.post("/assert", function(req, res) {
var options = {request_body: req.body};
sp.post_assert(idp, options, function(err, saml_response) {
if (err != null){
console.log("got here");
console.log(err);
return res.send(err);
}
// Save name_id and session_index for logout
// Note: In practice these should be saved in the user session, not globally.
name_id = saml_response.user.name_id;
session_index = saml_response.user.session_index;
res.send("Hello " +name_id +".");
//res.send("Hello #{saml_response.user.name_id}!");
});
});
// Starting point for logout
app.get("/logout", function(req, res) {
var options = {
name_id: name_id,
session_index: session_index
};
sp.create_logout_request_url(idp, options, function(err, logout_url) {
if (err != null)
return res.send(500);
res.redirect(logout_url);
});
});
var httpsOptions = {
key: fs.readFileSync('./key.pem')
, cert: fs.readFileSync('./certificate.crt')
}
var httpsServer = https.createServer(httpsOptions, app);
// app.listen(44301,console.log("App on 44301"));
httpsServer.listen(44301,console.log("App on 44301"));
I am working on implementing social network application using node.js and the source that I use is Building Node Application with MongoDB and Backbone' by Mike Wilson.However, I cannot figure out the how to resolve the error of the MemoryStore --var MemoryStore = require('connect').session.MemoryStore;
Also, I tried to comment it but the error with middleware appear
var Session = require('connect').middleware.session.Session;
Can I get your help please ?
Thanks in advance
Here is the code of app.js
var express = require('express');
var http = require('http');
var app = express();
var nodemailer = require('nodemailer');
var MemoryStore = require('connect').session.MemoryStore;
var dbPath = 'mongodb://10.168.122.123:27017/socialnet';
var fs = require('fs');
var events = require('events');
// Create an http server
app.server = http.createServer(app);
// Create an event dispatcher
var eventDispatcher = new events.EventEmitter();
app.addEventListener = function (eventName, callback) {
eventDispatcher.on(eventName, callback);
};
app.removeEventListener = function (eventName, callback) {
eventDispatcher.removeListener(eventName, callback);
};
app.triggerEvent = function (eventName, eventOptions) {
eventDispatcher.emit(eventName, eventOptions);
};
// Create a session store
app.sessionStore = new MemoryStore();
// Import the data layer
var mongoose = require('mongoose');
var config = {
mail: require('./config/mail')
};
// Import the model
var models = {
Account: require('./models/Account')(app, config, mongoose, nodemailer)
}
// Configure the application
app.configure(function(){
app.sessionSecret = 'SocialNet secret key';
app.set('view engine', 'jade');
app.use(express.static(__dirname + '/public'));
app.use(express.limit('1mb'));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
secret: app.sessionSecret,
key: 'express.sid',
store: app.sessionStore
}));
mongoose.connect(dbPath, function onMongooseError(err) {
if (err) throw err;
});
});
// Import the routes located in ./routes
fs.readdirSync('routes').forEach(function(file) {
if (file[0] == '.') return;
var routeName = file.substr(0, file.indexOf('.'));
require('./routes/' + routeName)(app, models);
});
// -----
// GET /
// -----
app.get('/', function(req, res){
res.render("index.jade", {layout: false});
});
// -------------------
// POST /contacts/find
// -------------------
app.post('/contacts/find', function(req, res) {
var searchStr = req.param('searchStr', null);
if (null == searchStr) {
res.send(400);
return;
}
models.Account.findByString(searchStr, function onSearchDone(err, accounts) {
if (err || accounts.length == 0) {
res.send(404);
} else {
// TODO: Check if these accounts were already contacts
// if so, mark them as isContact so the views/Contact
// knows not to add a addButton
res.send(accounts);
}
});
});
// Let the server listen to 8000 (instead of the app)
app.server.listen(8000);
console.log('SocialNet listening to port 8000');
Your problem:
app.use(app.router)
, mounts your routes in that position in the call chain. You have it before your session middleware, so there is no req.session yet. When you leave it out, your routes will be positioned whenever you do your first app.get (or app.post and so on). If you still wish to control where your routes should be,
you can just:
move app.use(app.router) below the session middleware.
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);
});
};
I'm use node.js + express + socket.io.
But when I'm tring to use cookies - I'm getting an error
no cookie transmitted
I have view all answers on this site. But don't find solution.
Here is my code of server:
// Require server config
var server_config = require('./config.json');
// Require express
var express = require("express");
var MemoryStore = express.session.MemoryStore;
var app = express();
var sessionStore = new MemoryStore();
// Configure app
app.configure(function () {
app.use(express.cookieParser());
app.use(express.session({secret: 'secret', key: 'express.sid'}));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname));
});
// Require socket IO and create server
var io = require('socket.io').listen(app.listen(server_config.port));
var history = {};
history.rooms = [];
var parseCookie = require('express/node_modules/cookie').parse;
io.set('authorization', function (data, accept) {
// check if there's a cookie header
if (data.headers.cookie) {
// if there is, parse the cookie
data.cookie = parseCookie(data.headers.cookie);
// note that you will need to use the same key to grad the
// session id, as you specified in the Express setup.
data.sessionID = data.cookie['express.sid'];
data.getSession = function (cb) {
sessionStore.get(data.sessionID, function (err, session) {
if (!err && !session) err = 'No session';
data.session = session;
cb(err, session);
});
}
} else {
// if there isn't, turn down the connection with a message
// and leave the function.
return accept('No cookie transmitted.', false);
}
// accept the incoming connection
accept(null, true);
});
// On connection actions
io.sockets.on('connection', function (socket) {
socket.handshake.getSession(function (error, session) {
console.log(error);
});
// Draw action
socket.on('drawClick', function (data) {
// Push element to the history
/*if (history.rooms[socket.room])
history.rooms[socket.room].push(data);*/
socket.broadcast.to(socket.room).emit('draw', {socket_id: socket.id, shape: data.shape, canvas_id: data.canvas_id, history: data.history});
});
// Subscribe to a room
socket.on('subscribe', function (data) {
socket.room = data.room;
socket.join(socket.room);
// If room history does not exists - create it
/*if (!history.rooms[socket.room])
history.rooms[socket.room] = [];
// If history exists - draw it
else
io.sockets.socket(socket.id).emit('history', {history: history.rooms[socket.room]});*/
});
// Note that it is not necessary to call socket.leave() during the disconnect event.
// This will happen automatically. Empty rooms will be automatically pruned so there is no need to manually remove them.
socket.on('unsubscribe', function (data) {
socket.leave(socket.room);
});
});
Here how I init on client:
io.connect(myprepa.config.site_url + ":" + myprepa.config.port);
Please help.
I am using socket.io's authorize method( In detail here) to check whether the user is authorized to use my app by checking the cookie associated with the user.
The above blog post is fairly straight forward but I am using cookieSessions to store session data in cookies. One good question is on stackoverflow but I can't figure that out.
I want to know how to decrypt cookieSession data to access the session data. A bit of my sample code:
io.set('authorization', function (data, accept) {
//Check cookie for session data
accept(null, true);});
In other words, how can I access the cookieSession in socket.io?
I use the following (with passport, but it doesn't matter):
io.set('authorization', function(data, accept) {
var getCookieSession = require('./lib/cookie_session');
var session = getCookieSession(data.headers, {
key: 'YOUR KEY',
secret: 'YOUR SECRET'
});
if (session.passport.user) {
accept(null, true);
} else {
accept(null, false);
}
});
and getCookieSession is the following module:
var connect = require('connect');
var cookieParser = connect.cookieParser;
var cookieSession = connect.cookieSession;
module.exports = function(headers, opts) {
var key = opts.key || '_session';
var secret = opts.secret || '';
var req = { headers: headers, originalUrl: "/" };
var res = { on: function() {} };
var next = function () {};
cookieParser(secret)(req, res, next);
cookieSession({ key: key })(req, res, next);
return req.session;
};