172

How can I authenticate a socket.io connection? My application uses a login endpoint from another server (python) to get a token, how can I get use that token whenever a user opens a socket connection on the node side?

io.on('connection', function(socket) {
    socket.on('message', function(message) {
        io.emit('message', message);
    });
});

And the client side:

var token = sessionStorage.token;
var socket = io.connect('http://localhost:3000', {
    query: 'token=' + token
});

If the token is created in python:

token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')

How can I use this token to authenticate a socket connection in node?

ROOT
  • 11,363
  • 5
  • 30
  • 45
el_pup_le
  • 11,711
  • 26
  • 85
  • 142

4 Answers4

347

It doesn't matter if the token was created on another server. You can still verify it if you have the right secret key and algorithm.

Implementation with jsonwebtoken module

client

const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000', {
  query: {token}
});

Server

const io = require('socket.io')();
const jwt = require('jsonwebtoken');

io.use(function(socket, next){
  if (socket.handshake.query && socket.handshake.query.token){
    jwt.verify(socket.handshake.query.token, 'SECRET_KEY', function(err, decoded) {
      if (err) return next(new Error('Authentication error'));
      socket.decoded = decoded;
      next();
    });
  }
  else {
    next(new Error('Authentication error'));
  }    
})
.on('connection', function(socket) {
    // Connection now authenticated to receive further events

    socket.on('message', function(message) {
        io.emit('message', message);
    });
});

Implementation with socketio-jwt module

This module makes the authentication much easier in both client and server side. Just check out their examples.

client

const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000');
socket.on('connect', function (socket) {
  socket
    .on('authenticated', function () {
      //do other things
    })
    .emit('authenticate', {token}); //send the jwt
});

Server

const io = require('socket.io')();
const socketioJwt = require('socketio-jwt');

io.sockets
  .on('connection', socketioJwt.authorize({
    secret: 'SECRET_KEY',
    timeout: 15000 // 15 seconds to send the authentication message
  })).on('authenticated', function(socket) {
    //this socket is authenticated, we are good to handle more events from it.
    console.log(`Hello! ${socket.decoded_token.name}`);
  });
Adam Reis
  • 4,165
  • 1
  • 44
  • 35
hassansin
  • 16,918
  • 3
  • 43
  • 49
  • I am facing an issue, even though there is not an incoming connection to the socket, when i fired up the socket server it still has the old token. is that weird ? – Lamour Oct 24 '16 at 02:09
  • What are the available options with io.connect in client api – tu4n Feb 09 '17 at 01:35
  • 4
    How would you reconnect to the server in case the user was originally unauthorized and it did not have secret at the first try? – sznrbrt Nov 15 '17 at 20:52
  • 21
    hello, i need to ask, token is required for one time on connection or on every emitting event? – Krunal Limbad Nov 02 '18 at 10:10
  • I want to ask the same as @KrunalLimbad I have been researching this forever, because adding the token for every emit is too much bloatware, but from what I've seen it seems the token has to always be sent with the emt() but im uncertain – Mr-Programs Jan 16 '19 at 01:29
  • 4
    Not fully related, but the token you receive from your endpoint you store in sessionStorage.token? This means that any JS running on your page can access the token right? What prevents external JS code (like a chrome extension) from reading the token and using it to login their own socket as if they are you? This is like a cookie with HttpOnly:false storing your acces. I am also trying to identify a socket connection by token from an external server but I have not found a way to keep the token secret once it reached the client-side, since ANY (other external) JS can read it too. :( Any idea's? – Dex Mar 03 '19 at 10:16
  • 4
    For anyone getting `Cannot read property 'on' of undefined`; just remove the `socket` from `function(socket)`. – Thomas Orlita Apr 24 '19 at 14:00
  • @Lamar you fixed your issue? – Sagar Nayak Jul 05 '19 at 11:12
  • @SagarNayak Sorry, I've moved away from that project, I can't recall if the issue was fixed – Lamour Jul 05 '19 at 17:49
  • Neither of these examples seem to work with the latest socket.io. – ThickMiddleManager Dec 20 '19 at 03:56
  • The SocketIO-JWT example is for socket.io ver. < 1.0. See the updated example on https://github.com/auth0-community/auth0-socketio-jwt – Alexander Elgin Jan 30 '20 at 06:06
  • Is this a good practice since it sets secret token in query string, aka URI? – Han Apr 12 '20 at 05:40
  • 3
    Adding sensitive data to an url is a bad pratice. You can read this post for a different approach. https://facundoolano.wordpress.com/2014/10/11/better-authentication-for-socket-io-no-query-strings/ – Mark E Sep 08 '20 at 20:32
  • With the first implementation how does the user know there is an error? The events of error and disconnect don't get called. – Jessica Dec 06 '20 at 17:13
  • I don't understand, How to authenticate while client still have no Jwt? Should i send token along each emit? – Aqilhex Mar 09 '21 at 20:18
  • This is middleware, it will be executed once on connection: `Note: this function will be executed only once per connection (even if the connection consists in multiple HTTP requests).` https://socket.io/docs/v3/middlewares/#Registering-a-middleware – sr9yar Jun 01 '21 at 10:58
  • @Dex external code is not allowed to read cookies set with httpOnly flag, which is an absolute must for cookies which contain jwt – Jos Nov 08 '22 at 13:16
  • @JosFaber Hi Jos, I did not say that they where. I meant that the token stored in the sessionStorage as el_pup_le wrote is readable to ANY JS that runs on the client and therefore seems to me to be similar to a cookie with HttpOnly:false storing the token. – Dex Nov 10 '22 at 22:58
  • `socketio-jwt` is no longer supported, and should be removed. – vitaly-t Jun 17 '23 at 19:19
5

Since I don't have enough reputation to add a comment to accepted answer:

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

Is not actually apart of auth0 's repo and is a third-party community based package.

Answer above should be updated as the link to auth0 repo is a page 404.

RJA
  • 129
  • 1
  • 7
4

Write a middleware:

const jwt = require("jsonwebtoken");

const authSocketMiddleware = (socket, next) => {
  // since you are sending the token with the query
  const token = socket.handshake.query?.token;
  try {
    const decoded = jwt.verify(token, process.env.TOKEN_SECRET_KEY);
    socket.user = decoded;
  } catch (err) {
    return next(new Error("NOT AUTHORIZED"));
  }
  next();
};

module.exports = authSocketMiddleware;

Use it inside socket server

socketServer = (server) => {
  const io = require("socket.io")(server, {
    cors: {
      origin: "*",
      methods: ["GET", "POST"],
    },
  });
  // before connection use the middleware
  io.use((socket, next) => {
    authSocketMiddleware(socket, next);
  });
  io.on("connection", (socket) => {
    console.log("user connected", socket.id);
  });
};
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
3

Here I wrote the indepth article on how to authenticate the user on the sockets and also save the user's data

https://medium.com/@tameemrafay/how-to-authenticate-user-and-store-the-data-in-sockets-19b262496feb

CLIENT SIDE CODE

import io from "socket.io-client";

const SERVER = "localhost:4000";
const socket = io(SERVER, {
    auth: {
      token: "2c87b3d5412551ad69aet757f81f6a73eb919e5b02467aed419f5b2a9cce2b5aZOzgaM+bpKfjdM9jvez37RTEemAp07kOvEFJ3pBzvj8="
    }
  });

 socket.once('connect', (socketConnection) => {
    console.log('socket connected', socketConnection);
  })
 
// Emit the message and receive data from server in callback
 socket.emit("user send message", {message: "Hi you there" }, callback => {
    if (callback) {
      console.log("--- response from server", callback);
     }
  });

SERVER SIDE CODE

const initializeSockets = (io) => {
  io.on('connection', (socket) => {

  decryptAndStoreUserData(socket,io);
  
}

const decryptAndStoreUserData = async (socket,io) => {

    const { token } = socket.handshake.auth; // receive the token from client

    // here i decypt the auth token and get the user data
    const genToken = new Middlewares().token();
    const userData = genToken.decryptTokenForSockets(token);

    // save the data of user in socket
    socket.data.user = userData;

  }
Muhammad Tameem Rafay
  • 3,602
  • 2
  • 21
  • 32
  • What if multiple users are connecting using different clients, will saving data in socket.data.user wont overide other? Or each connection is an instance of socket and each instance will hold its own user info for that connection? – Marius Aug 24 '23 at 10:25
  • 1
    yes, you are correct. Each user that is connected with socket has its own session/user info. – Muhammad Tameem Rafay Aug 25 '23 at 11:04