I've recently written a session management plugin in node.js for Muneem web framework. Here is pseudo code to create a new session;
function createSession(){
// read encrypted session-id from the request
if( sessionId ){
// decrypt it
if (decryptedSessionId ) {
//read session detail from the store
options.store.get(decryptedSessionId, (err, sessionFromStore) => {
if(err){
throw Error(err);
}else if( sessionFromStore){
if( shouldRenew(sessionFromStore) ){
//delete previous session
options.store.destroy(sessionFromStore.id, err=> {
//update the session object in memory
});
}
}else{ //session detail is not present in store
// create new session
}
});
} else { //invalid or tempered session
// throw error
}
}else{ //session-id is not presnet in request
// create new session
}
}
As you can notice, I'm renewing a session when it is valid and satisfy certain conditions by deleting the previous session. But I don't update it in the store immediately. Instead, I update the session information in the store and set the cookies when the response is being sent to the client.
Now suppose a condition, when the server receives multiple requests with the same session-id which is eligible to renew. I renew the session on the first request.
Scenarios
Session is not updated in store. So I'll renew the previous session with another new session id. A user will have multiples session-id in this case.
Session is updated in the store. Now, the previous session will not be available in the store. I'll have to ask the user to login again if it is authorized session. Or I'll create another session.
How to handle this race condition?
Here is the full code, in case we need.
This situation seems not to occur if we authenticate the main route only.
I was previously thinking to authenticate every request. A server can receive multiple requests with the same session ID only on page load which includes access to the main route along with static files like CSS, js, etc. Static files are not required to be authenticated. Hence race condition should not occur.
Related
I have a simple application, which will login a user and based on his role will let him use a specific API (say normal/admin..view/update respectively). I am using MYSQL to Authenticate users and connection pool to manage many connections to app.
This is how i am managing session,
app.use(expressSession({
cookieName: 'session',
secret: 'mysecret',
duration: 15 * 60 * 1000,
}));
At login,
app.post('/login',function(req,res){
Authenticate(req.body.username,req.body.password,function(err,fname,user){
if(!err) {
req.session.name=user;
res.send("Welcome " + fname);
}
else{
res.send("There seems to be an issue with the username/password combination that you entered");
}
});
});
and whenever i want to access any specific API,i will check for this session name.
app.post('/updateInfo',function(req,res){
if(req.session.name){
// My logic - i ll return the role by searching in db using this req.session.name, if admin will allow him.
}
});
I will destroy the session in logout.
I have two questions since i don't understand the mechanism of the session in node js.
Now suppose i login as admin user and after authentication export
the cookie (say using edit this cookie chrome extension) and logout.
Then login as normal user and import the cookie(admin cookie), will
there be any breach (because now the session will contain admin username)?
Suppose if i want to avoid multiple
connections for a same user (user using different browser/mobile to login), can i just add this piece of code?
if(req.session.name == req.body.username)
{
req.session.destroy();
}
and then continue to authenticate? What are the drawbacks here? Why will it work or not work?
I really would love to make this stateless(don't want to persist state/session in db) because i want performance. Can i do something that is stateless and still works even when there are 100s of concurrent users?
I am trying to implement a login system that sends a confirmation email to the user in case he logs in from a new computer/browser.
I am using Nodejs, AngularJS and PassportJS.
Any pointers to where I could find resources for this will be greatly appreciated.
The client side can detect stuff like os/browser, so you can just POST that data up to the server whenever the client loads. Other than that, you can match usernames with IP-adresses, but if you're storing that kind of information you ought to hash the information before saving it.
Could be as simple as setting a session variable (https://github.com/expressjs/session)
if(req.user){ // so that it only triggers when the user has actually logged in
if(!req.session.thisBrowser) {
req.session.thisBrowser = true || 'this computer/browser' || 'whatever';
req.user.email('You have been logged in from ...'); // do your thing
}
}
For logged in users only, I want to somehow notify them if they have any e.g. new notifications.
For example, say a member has sent them a private message, I want to tell the user that they have a new message to view (assuming they have not refreshed the page).
With Nodejs and redis, how would I go about doing this?
Note: I only need nodejs to send a small json to the user saying they have a new message.
The workflow is as follows that I was thinking:
1. user is logged in, a new message is sent to them.
2. somehow using nodejs and redis and long-polling, nodejs communicates back to the logged in users browser they have a pending message.
3. when nodejs sends this push notification, I then call another javascript function that will call a rest service to pull down additional json with the message.
I am integrating nodejs into an existing application, so I want to keep it as simple as possible with nodejs responsible for only notifying and not doing any additional logic.
Can someone outline how I should get going with this?
Should I be using redis http://redis.io/topics/pubsub somehow?
I'm not really sure how that works even after reading the page about it.
If you are integrating your node service into an existing application I would rather use some sort of messaging system to communicate messages from that application to node instead of a DB, even an in-memory DB. For clarity, I will assume you can use rabbitmq. If you do need to use redis, you will just need to find a way to use its publishing instead of rabbitmq publishing and corresponding node-side subscription, but I would imagine that the overall solution would be identical.
You need the following modules:
rabbitmq server (installation complexity about the same as for redis)
rabbitmq library in your external application to send messages, most languages are supported
rabit.js module for node to subscribe to messages or to communicate back to the external application
socket.io module for node to establish real-time connection between the node server and clients
I will also assume that both your external application and your node server have access to some shared DB (which can be redis), where node client session information is stored (e.g. redis-session-store for node). This would allow to use sessionId to validate who the message is for, if the user in the session is logged in and if certain users need to be sent notifications at all (by an external app).
This is how your stack might look like (unpolished):
Define a publisher in node to notify your external application that it needs to start/stop sending messages for a given sessionId. I will assume that for a given sessionId the user information can be recovered on either side (node or external application) from the shared DB and the user can be validated (here for simplicity by checking session.authenticated_user). Also define a subscriber to listen to incoming messages for the users:
var context = require('rabbit.js').createContext();
var pub = context.socket('PUB');
var sub = context.socket('SUB');
Define a socket.io connection(s) from your node server to the clients. As soon the client's web page is (re)loaded and io.connect() is called the below code will be executed (see clinet side at the end of the answer). As a new connection is established, validate the user is logged in (meaning its credentials are in the session), register the socket handler and publish a notification to the external application to start sending messages for this sessionId. The code here assumes a page reload on login/logout (and thus new socket.io session). If this is not the case, just emit a corresponding socket.io message from the client to node and register a handler in the method below in the same way as it is done for a new connection (this is beyond the scope of this example):
var sessionStore = undefined; // out-of-scope: define redis-session-store or any other store
var cookie = require("cookie"),
parseSignedCookie = require('connect').utils.parseSignedCookie;
// will store a map of all active sessionIds to sockets
var sockets = {};
// bind socket.io to the node http server
var io = require('socket.io').listen(httpServer);
// assumes some config object with session secrect and cookie sid
io.sockets.on("connection", function(socket) {
if (socket.handshake.headers.cookie) {
var cks = cookie.parse(socket.handshake.headers.cookie);
var sessionId = parseSignedCookie(cks[config.connectSid], config.sessionSecret);
// retrieve session from session store for sessionId
sessionStore.get(sessionId, function(err, session) {
// check if user of this session is logged in,
// define your elaborate method here
if (!err && session.authenticated_user) {
// define cleanup first for the case when user leaves the page
socket.on("disconnect", function() {
delete sockets[sessionId];
// notify external app that it should STOP publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'stop', reason: 'user disconnected'}), 'utf8');
});
});
// store client-specific socket for emits to the client
sockets[sessionId] = socket;
// notify external app that it should START publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'start'}), 'utf8');
});
}
});
}
});
Connect subscriber to the rabbitmq exchange to catch messages and emit them to clients:
sub.connect('messages_exchange', function() {
sub.on("readable", function() {
// parse incoming message, we need at least sessionId
var data = JSON.parse(sub.read());
// get socket to emit for this sessionId
var socket = sockets[data.sessionId];
if (socket) {
socket.emit("message", data.message);
} else {
// notify external app that it should STOP publishing
pub.connect('user_exchange', function() {
pub.write(JSON.stringify({sessionId: sessionId, action: 'stop', reason: 'user disconnected'}), 'utf8');
});
// further error handling if no socket found
}
});
});
Finally your client will look roughly like this (here in Jade, but that's just because I already have this whole stack along these lines):
script(src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js")
script(src="/socket.io/socket.io.js")
script(type='text/javascript').
$(function(){
var iosocket = io.connect();
iosocket.on('connect', function () {
// do whatever you like on connect (re-loading the page)
iosocket.on('message', function(message) {
// this is where your client finally gets the message
// do whatever you like with your new message
});
});
// if you want to communicate back to node, e.g. that user was logged in,
// do it roughly like this
$('#btnSend').click(function(event) {
iosocket.send('a message back to the node server if you need one');
});
});
Here is also a really nice explanation from Flickr on how they created a highly available and scalable push notification system with NodeJS and Redis.
http://code.flickr.net/2012/12/12/highly-available-real-time-notifications/
I would like to save some user sensitive data in the handshake object. The server side code looks something like this:
io.configure(function (){
io.set('authorization', function (handshakeData, callback) {
var objAuthorized = IsUserAuthorized(handshakeData);
if (objAuthorized.authorized) {
handshakeData.password = objAuthorized.password; // Store sensitive data inside handshake
handshakeData.email = objAuthorized.email; // Store sensitive data inside handshake
callback(null, true);
} else {
callback(null, false);
}
});
});
io.sockets.on('connection', function (socket) {
socket.on('do something', function() {
if DoSomething(socket.handshake.password, socket.handshake.email) {
// do something here
}
});
});
The above example was taken from https://github.com/LearnBoost/socket.io/wiki/Authorizing .
Is it safe to save sensitive data in the handshake object? Can a client somehow modify this data during the lifetime of his socket connection?
Thanks
As far as know, there hasn't been any problem with this, a user can't get privileges from the authorization in socket.io .. but its not considered a good way or good practice to do this.
Most of developers won't do this, they handle authentication/authorization in other part of their application and leave to cookies and sessions validation on socket.io. This will also remove an overhead on the socket.io server.
updated
the following examples show how to work with sessions express/socket.io
some useful links :
http://howtonode.org/socket-io-auth
https://github.com/LearnBoost/socket.io/wiki/Authorizing
Note that you have to authenticate the user in a common login form, matching credentials against a database, if you have worked previously with PHP and user authentication you wont have a problem. After matching the user credentials you set the sessionID and any other data, then the rest is handled by express.
I am using nodejs, express and connect-memcaced to handle my sessions. I've made a login script which executes perfectly every time, sets the session data and marks the user as logged in and than redirects him back to the page he logged in from. What happens on occasion (not always) is that after he is redirected and the page loads the cookies sessionID changes and the user is off course not logged in anymore. I have failed finding the reason why this happens and why it doesn't happen every time.
Code snipet of the login function:
DB.getOne('User',{filters:{'primary':{email:req.body.email}}}, function(err,data){
if(data[0] && data[0].active == 1){
var encodedPass = self.encodePass(req.body.pass,req.body.email);
if(encodedPass == data[0].pass){
req.session.pr.user = data[0];
req.session.pr.user.status = true;
res.writeHead(302, {
'Location': goTo
});
res.end();
}
}
});
Looking directly into memcached I can see that this executes perfectly and the data is always saved into memcached under the original sessionID. For some reason the redirect must be changing the sessionID
I often find that this happens to me when I do not notice that I have restarted the development server. Remember that the session cookies are stored in memory, so when the server is restarted, all the sessions will be forgotten and the user will be assigned a new session automatically.