Socket.IO Authentication

79,938

Solution 1

Use connect-redis and have redis as your session store for all authenticated users. Make sure on authentication you send the key (normally req.sessionID) to the client. Have the client store this key in a cookie.

On socket connect (or anytime later) fetch this key from the cookie and send it back to the server. Fetch the session information in redis using this key. (GET key)

Eg:

Server side (with redis as session store):

req.session.regenerate...
res.send({rediskey: req.sessionID});

Client side:

//store the key in a cookie
SetCookie('rediskey', <%= rediskey %>); //http://msdn.microsoft.com/en-us/library/ms533693(v=vs.85).aspx

//then when socket is connected, fetch the rediskey from the document.cookie and send it back to server
var socket = new io.Socket();

socket.on('connect', function() {
  var rediskey = GetCookie('rediskey'); //http://msdn.microsoft.com/en-us/library/ms533693(v=vs.85).aspx
  socket.send({rediskey: rediskey});
});

Server side:

//in io.on('connection')
io.on('connection', function(client) {
  client.on('message', function(message) {

    if(message.rediskey) {
      //fetch session info from redis
      redisclient.get(message.rediskey, function(e, c) {
        client.user_logged_in = c.username;
      });
    }

  });
});

Solution 2

I also liked the way pusherapp does private channels.enter image description here

A unique socket id is generated and sent to the browser by Pusher. This is sent to your application (1) via an AJAX request which authorizes the user to access the channel against your existing authentication system. If successful your application returns an authorization string to the browser signed with you Pusher secret. This is sent to Pusher over the WebSocket, which completes the authorization (2) if the authorization string matches.

Because also socket.io has unique socket_id for every socket.

socket.on('connect', function() {
        console.log(socket.transport.sessionid);
});

They used signed authorization strings to authorize users.

I haven't yet mirrored this to socket.io, but I think it could be pretty interesting concept.

Solution 3

I know this is bit old, but for future readers in addition to the approach of parsing cookie and retrieving the session from the storage (eg. passport.socketio ) you might also consider a token based approach.

In this example I use JSON Web Tokens which are pretty standard. You have to give to the client page the token, in this example imagine an authentication endpoint that returns JWT:

var jwt = require('jsonwebtoken');
// other requires

app.post('/login', function (req, res) {

  // TODO: validate the actual user user
  var profile = {
    first_name: 'John',
    last_name: 'Doe',
    email: '[email protected]',
    id: 123
  };

  // we are sending the profile in the token
  var token = jwt.sign(profile, jwtSecret, { expiresInMinutes: 60*5 });

  res.json({token: token});
});

Now, your socket.io server can be configured as follows:

var socketioJwt = require('socketio-jwt');

var sio = socketIo.listen(server);

sio.set('authorization', socketioJwt.authorize({
  secret: jwtSecret,
  handshake: true
}));

sio.sockets
  .on('connection', function (socket) {
     console.log(socket.handshake.decoded_token.email, 'has joined');
     //socket.on('event');
  });

The socket.io-jwt middleware expects the token in a query string, so from the client you only have to attach it when connecting:

var socket = io.connect('', {
  query: 'token=' + token
});

I wrote a more detailed explanation about this method and cookies here.

Solution 4

Here is my attempt to have the following working:

  • express: 4.14
  • socket.io: 1.5
  • passport (using sessions): 0.3
  • redis: 2.6 (Really fast data structure to handle sessions; but you can use others like MongoDB too. However, I encourage you to use this for session data + MongoDB to store other persistent data like Users)

Since you might want to add some API requests as well, we'll also use http package to have both HTTP and Web socket working in the same port.


server.js

The following extract only includes everything you need to set the previous technologies up. You can see the complete server.js version which I used in one of my projects here.

import http from 'http';
import express from 'express';
import passport from 'passport';
import { createClient as createRedisClient } from 'redis';
import connectRedis from 'connect-redis';
import Socketio from 'socket.io';

// Your own socket handler file, it's optional. Explained below.
import socketConnectionHandler from './sockets'; 

// Configuration about your Redis session data structure.
const redisClient = createRedisClient();
const RedisStore = connectRedis(Session);
const dbSession = new RedisStore({
  client: redisClient,
  host: 'localhost',
  port: 27017,
  prefix: 'stackoverflow_',
  disableTTL: true
});

// Let's configure Express to use our Redis storage to handle
// sessions as well. You'll probably want Express to handle your 
// sessions as well and share the same storage as your socket.io 
// does (i.e. for handling AJAX logins).
const session = Session({
  resave: true,
  saveUninitialized: true,
  key: 'SID', // this will be used for the session cookie identifier
  secret: 'secret key',
  store: dbSession
});
app.use(session);

// Let's initialize passport by using their middlewares, which do 
//everything pretty much automatically. (you have to configure login
// / register strategies on your own though (see reference 1)
app.use(passport.initialize());
app.use(passport.session());

// Socket.IO
const io = Socketio(server);
io.use((socket, next) => {
  session(socket.handshake, {}, next);
});
io.on('connection', socketConnectionHandler); 
// socket.io is ready; remember that ^this^ variable is just the 
// name that we gave to our own socket.io handler file (explained 
// just after this).

// Start server. This will start both socket.io and our optional 
// AJAX API in the given port.
const port = 3000; // Move this onto an environment variable, 
                   // it'll look more professional.
server.listen(port);
console.info(`🌐  API listening on port ${port}`);
console.info(`🗲 Socket listening on port ${port}`);

sockets/index.js

Our socketConnectionHandler, I just don't like putting everything inside server.js (even though you perfectly could), especially since this file can end up containing quite a lot of code pretty quickly.

export default function connectionHandler(socket) {
  const userId = socket.handshake.session.passport &&
                 socket.handshake.session.passport.user; 
  // If the user is not logged in, you might find ^this^ 
  // socket.handshake.session.passport variable undefined.

  // Give the user a warm welcome.
  console.info(`⚡︎ New connection: ${userId}`);
  socket.emit('Grettings', `Grettings ${userId}`);

  // Handle disconnection.
  socket.on('disconnect', () => {
    if (process.env.NODE_ENV !== 'production') {
      console.info(`⚡︎ Disconnection: ${userId}`);
    }
  });
}

Extra material (client):

Just a very basic version of what the JavaScript socket.io client could be:

import io from 'socket.io-client';

const socketPath = '/socket.io'; // <- Default path.
                                 // But you could configure your server
                                // to something like /api/socket.io

const socket = io.connect('localhost:3000', { path: socketPath });
socket.on('connect', () => {
  console.info('Connected');
  socket.on('Grettings', (data) => {
    console.info(`Server gretting: ${data}`);
  });
});
socket.on('connect_error', (error) => {
  console.error(`Connection error: ${error}`);
});

References:

I just couldn't reference inside the code, so I moved it here.

1: How to set up your Passport strategies: https://scotch.io/tutorials/easy-node-authentication-setup-and-local#handling-signupregistration

Solution 5

This article (http://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share-redi/) shows how to

  • store sessions of the HTTP server in Redis (using Predis)
  • get these sessions from Redis in node.js by the session id sent in a cookie

Using this code you are able to get them in socket.io, too.

var io = require('socket.io').listen(8081);
var cookie = require('cookie');
var redis = require('redis'), client = redis.createClient();
io.sockets.on('connection', function (socket) {
    var cookies = cookie.parse(socket.handshake.headers['cookie']);
    console.log(cookies.PHPSESSID);
    client.get('sessions/' + cookies.PHPSESSID, function(err, reply) {
        console.log(JSON.parse(reply));
    });
});
Share:
79,938
Ryan
Author by

Ryan

Getting myself up to speed with ruby; have some C/C++ experience

Updated on September 26, 2020

Comments

  • Ryan
    Ryan over 3 years

    I am trying to use Socket.IO in Node.js, and am trying to allow the server to give an identity to each of the Socket.IO clients. As the socket code is outside the scope of the http server code, it doesn't have easy access to the request information sent, so I'm assuming it will need to be sent up during the connection. What is the best way to

    1) get the information to the server about who is connecting via Socket.IO

    2) authenticate who they say they are (I'm currently using Express, if that makes things any easier)

  • Shripad Krishna
    Shripad Krishna about 13 years
    This is awesome. But it would be easier to just use cookies if your app server and websocket server aren't separated. But you generally would like to separate the two(it will be easier to scale the socket server if separated). So it is good :)
  • Alfred
    Alfred about 13 years
    @Shripad you are completely true and I also really like your implementation :P
  • Alfred
    Alfred over 12 years
    There is a new interesting link about this => danielbaulig.de/socket-ioexpress
  • Shripad Krishna
    Shripad Krishna over 12 years
    aha! That link is really good. This is outdated (uses Socket.IO 0.6.3)! Essentially the same concept. Fetch cookie, check in session store and authenticate :)
  • ZiTAL
    ZiTAL almost 12 years
    io.socket.sessionid in my case
  • Shripad Krishna
    Shripad Krishna over 11 years
    @NightWolf it should work because you are fetching the cookie in javascript not in flash (actionscript). GetCookie is javascript function.
  • Admin
    Admin over 9 years
    This isn't even an attempt at an answer. This is not authentication this is simply making a connection.
  • Carpetfizz
    Carpetfizz over 9 years
    Hey! Quick question, why are you sending the profile with the token if it can't be decoded on the client?
  • José F. Romaniello
    José F. Romaniello almost 9 years
    It can. JWT is just base64 , digital signed. The client can decode it, but it can't validate the signature in this example.
  • Nick Pineda
    Nick Pineda about 8 years
    Looks like if you just plug in the same code you are using to validate your Node.js endpoints(but you'll have to tweak any parts you are handling the request object) you could just reuse your token for your routes.
  • Pro Q
    Pro Q over 7 years
    @Alfred that link seems to be dead now :(
  • Tom
    Tom about 6 years
    @Alfred's link is valid again 2018-02-01
  • captDaylight
    captDaylight over 3 years
    @JoséF.Romaniello your article is down, I like your approach though.