Вопрос

I have an Express app up and running that does authentication with Passport.js (with a MongoDB backend).

Everything works great, but now I face another challenge:

I want to have some kind of a "chat" functionality in my project, and for that I'm using Socket.IO (delivering messages in real-time).

The page verifies that user is logged in before loading, but that still can be bypassed.

I want the Socket.IO stream to be secured and authorized as well.

How can I integrate the Socket.IO chat system to my Passport.js-based authentication?

Это было полезно?

Решение

I'm not sure exactly how passport works but assuming it sets a cookie on the client with their session id then the following solution should work. It seems hacky but I haven't seen a more elegant solution out there.

var connect = require('express/node_modules/connect'),
    parseSignedCookie = connect.utils.parseSignedCookie,
    Cookie = require('express/node_modules/cookie'),
    store = YOUR_SESSION_STORE_INSTANCE, // i.e. redis-store, memory, or whatever 
    sessionKey = "YOUR SESSION KEY", // defaults to connect.sid
    sessionSecret = "YOUR SESSION SECRET";

var verifyCookie = function(data, callback){
    try{
        var cookie = Cookie.parse(data.headers.cookie);
        var sessionID = parseSignedCookie(cookie[sessionKey], sessionSecret);
        store.get(sessionID, callback);
    }catch(e){
        callback(e);
    }
};

// set up socket.io to validate cookies on an authorization request
// this assumes you've assigned your socket.io server to the io variable
io.configure(function(){
    io.set("authorization", function(handshake, accept){
        if(handshake.headers.cookie){
            verifyCookie(handshake, function(error, session){
                if(error || !session)
                    accept("Invalid authentication", false);
                else
                    accept(null, true);
            });
        }else{
            accept("Invalid authentication", false);
        }        
    });
});

I'm not 100% sure here but if this doesn't pick up the session correctly you may have to change the session store used above to passport's session store instead of the default express store or whatever you were using.

Hope that helps. If this works or if you find the correct solution please post your findings as we're going to likely move to passport in the future as well and it'll be great to know how to patch our existing code.

Edit: After some more poking around it looks like after you get the session data from the cookie as shown above you'll have to find the user id property and pass that data to passport. The example they show at http://passportjs.org/guide/configure/ is:

passport.deserializeUser(function(id, done) {
    User.findById(id, function(err, user) {
        done(err, user);
    });
});

Edit 2: Another option would be to sidestep this problem entirely. Before implementing the solution listed above we went a different route which involved one time tokens stored in redis. On a high level the process worked like this:

  1. The client first requests a token (implemented as a uuid) from the server via a regular HTTP request. This routes the request through express's middleware and gives easy access to all of the session data.
  2. When the token is generated it is stored in redis as a key-value pair where the token is the key and the session data is the value.
  3. The token is then passed back to the user which the socket.io client appends to the url as a GET parameter when making the initial connection "upgrade" request.
  4. When socket.io receives the authorization request instead of verifying the cookie it parses the url, pops off the token as a GET parameter, and attempts to query redis for the key-value pair keyed by the token. If the redis query fails or returns null data then you deny the authorization request. If the redis request succeeds then you have full access to all of the session data and now you can delete the key-value pair in redis.

A possible gotcha here is that you need to force the socket.io client to force a new connection on every connect attempt. Otherwise it attempt to use the same connection and therefore the same token. If you delete tokens after validating them once this will prevent sockets from reconnecting. Depending on your application this may be the desired behavior. It was for ours so we left it as is, but it's still something worth knowing.

This approach also makes your WS authentication mechanism independent of your WS library. Sockjs doesn't provide access to the cookie on connection requests for security reasons so this approach made our switch from socket.io to sockjs easier as well.

Другие советы

If you're developing an app based on Express ver 4.x and Socket.io ver 1.x you might want to read this article: http://mykospark.net/2014/07/authentication-with-socket-io-1-0-and-express-4-0

In my case the code of the authentication procedure looked as follows:

io.use(function(socket, next) {
    var handshake = socket.handshake;

    if (handshake.headers.cookie) {
        var req = {
            headers: {
                cookie: handshake.headers.cookie,
            }
        }

        cookieParser(config.session.secret)(req, null, function(err) {
            if (err) {
                return next(err);
            }
            var sessionID = req.signedCookies[config.session.name] ||
                            req.cookies[config.session.name];

            var sessionStore = new MongoStore({ db: global.db});
            sessionStore.get(sessionID, function (err, session) {
                if (err) {
                    return next(err);
                }

                // userData bellow is written once the Express session is created
                if (session && session.userData) { 
                    next();
                } else {
                    return next(new Error('Invalid Session'));
                }
            })
        });
    } else {
        next(new Error('Missing Cookies'));
    }
});
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top