If you are working with Express.js you have for sure heard about socket.io, it is an amazing event-based bi-directional communication layer for real-time web applications, and it is pretty simple to use, but it is often configured incorrectly leaving our application vulnerable.

Today I’m going to teach you how to secure your socket.io on an Express.js server using jsonwebtoken, there are more steps to get a secure configuration like allowing only your app origins that we will not discuss here on this post, if you want more information about it, you can go to socket.io server docs.

In order to secure you socket with token authentication, your app must be using jsonwebtoken to authenticate your users, so, you already have a middleware to verify and to sign the token.

The first thing you need to do is install socket.io and jsonwebtoken with npm, I assume you already have an Express.js app to follow this tutorial.

npm install socket.io --save
npm install jsonwebtoken --save

Start by configuring Express.js then go through scoket.io, the next file is just a basic configuration to run the express server.

// server.js

const app = require('./app');
const server = require('http').Server(app);
const io = require('socket.io')(server);
const jwt = require('jsonwebtoken');
const port = process.env.PORT || 3000;

server.listen(port, () => {
 console.log(`Server running on port: ${server.address().port}`);
});

// socket.io config here

Notice that the app is just the ‘express()’ app exported from another file with routes and any other middleware configurations, I like to keep it in another file, I will omit the code since is irrelevant for this guide, we just created an HTTP server then configured socket.io to start on that server.

Now it’s time for some socket.io code, socket.io’s middlewares work like Express.js middlewares, they are just chained functions, we will define our middleware to verify the token before the user can connect to our socket, but first, let’s show you how to send the token from the client side.

// frontend (client side)

// send the token on connection
socket = io(http://your-server-url, {
     query: {
       Token: ‘yourTokenHere’,
     },
   });

// If you refresh your token, update it upon reconnection attempt
socket.on('reconnect_attempt', () => {
     this.socket.io.opts.query = {
       token: ‘yourRefreshedToken’,
     };
   });

If you want more information about socket.io client, go to socket.io client docs.

// server.js (server side)

const app = require('./app');
const server = require('http').Server(app);
const io = require('socket.io')(server);
const jwt = require('jsonwebtoken');
const port = process.env.PORT || 3000;

server.listen(port, () => {
 console.log(`Server running on port: ${server.address().port}`);
});

// scoket.io middleware

io.use((socket, next) => {
 const token = socket.handshake.query.token;

 // verify token
 jwt.verify(token, process.env.TOKEN_SECRET, (err, decoded) => {
   if(err) return next(err);
   // set the user’s mongodb _id to the socket for future use
   socket._id = decoded._id;
   next();
 });
});

// chat.events.js (socket events)
require('./socket.io/chat.events')(io);

We are done with our middleware to protect our socket with token authentication, first we get the token sent by the client, then we verify it, if an error has occurred we sent it to the client ‘next(err)’ and the user won’t be able to connect to our socket, if the token is the token is successfully verified we store our user’s _id on the user’s socket instance then we call next() to let the user connect to our socket.

There’s something left, we need to handle the user’s connection to our socket and add some events, that’s what we will do in our chat.events.js file.

The next file uses the mongoose framework, on a simple function to store a document in our database, it is just an example, you can execute any code you want in your socket events.

// chat..events.js

const Message = require('../models/message.model');
module.exports = (io) => {
 io.on('connection', (socket) => {
   console.log('a user connected');

   socket
     .on('new message', newMessage)
     .on('disconnect', disconnect);

   function newMessage(message, callback) {
     // we have access to the user’s _id we just stored on the
     // verifyToken middleware

     message.author = socket._id;

     Message.create(message, (err, createdMessage) => {
       if (err) return callback(err);

       socket
         .to(createdMessage.chat)
         .emit('new message', createdMessage);

         callback(createdMessage);
     });
     console.log('new message: ', message);
   }

   function disconnect() {
     console.log('user disconnected');
   }
 });
}

Here we just handle the user’s connection to the socket, then we can define any events we need to handle from the client, I just added a ‘new message’ event just to make an example, it receives the message’s data from the client, but the user’s _id is taken from the socket instance, remember that we stored it before when we verified the token.

We don’t want the client to send any _ids to store data on our server, so we make sure that is the actual user who’s sending that event by getting the _id from the token. Again, we create the message then if there is an error we sent it to the client as a callback, callbacks are optional in socket events, but they are useful when you need some response from the event, in this case, we want to know is the message was sent to notify the client.

If there were no errors we just sent the message to the other users in the room, then we sent the message as a callback to the client, notice that you need to connect the users to that room to receive the message, you can do that by sending the chat _id to the socket like we sent the token, then join any channel you want, if you are interested you can check the socket.io rooms & namespaces docs.

I hope you could understand how and why you should secure your socket, it is pretty easy and your socket will be open only for the users registered on your application, stay in touch for more posts!