Middlewares

A middleware function is a function that gets executed for every incoming connection.

Middleware functions can be useful for:

  • logging
  • authentication / authorization
  • rate limiting

Note: this function will be executed only once per connection (even if the connection consists in multiple HTTP requests).

Registering a middleware

A middleware function has access to the Socket instance and to the next registered middleware function.

  1. io.use((socket, next) => {
  2. if (isValid(socket.request)) {
  3. next();
  4. } else {
  5. next(new Error("invalid"));
  6. }
  7. });

You can register several middleware functions, and they will be executed sequentially:

  1. io.use((socket, next) => {
  2. next();
  3. });
  4. io.use((socket, next) => {
  5. next(new Error("thou shall not pass"));
  6. });
  7. io.use((socket, next) => {
  8. // not executed, since the previous middleware has returned an error
  9. next();
  10. });

Please make sure to call next() in any case. Otherwise, the connection will be left hanging until it is closed after a given timeout.

Important note: the Socket instance is not actually connected when the middleware gets executed, which means that no disconnect event will be emitted if the connection eventually fails.

For example, if the client manually closes the connection:

  1. // server-side
  2. io.use((socket, next) => {
  3. setTimeout(() => {
  4. // next is called after the client disconnection
  5. next();
  6. }, 1000);
  7. socket.on("disconnect", () => {
  8. // not triggered
  9. });
  10. });
  11. io.on("connection", (socket) => {
  12. // not triggered
  13. });
  14. // client-side
  15. const socket = io();
  16. setTimeout(() => {
  17. socket.disconnect();
  18. }, 500);

Sending credentials

The client can send credentials with the auth option:

  1. // plain object
  2. const socket = io({
  3. auth: {
  4. token: "abc"
  5. }
  6. });
  7. // or with a function
  8. const socket = io({
  9. auth: (cb) => {
  10. cb({
  11. token: "abc"
  12. });
  13. }
  14. });

Those credentials can be accessed in the handshake object on the server-side:

  1. io.use((socket, next) => {
  2. const token = socket.handshake.auth.token;
  3. // ...
  4. });

Handling middleware error

If the next method is called with an Error object, the connection will be refused and the client will receive an connect_error event.

  1. // client-side
  2. socket.on("connect_error", (err) => {
  3. console.log(err.message); // prints the message associated with the error
  4. });

You can attach additional details to the Error object:

  1. // server-side
  2. io.use((socket, next) => {
  3. const err = new Error("not authorized");
  4. err.data = { content: "Please retry later" }; // additional details
  5. next(err);
  6. });
  7. // client-side
  8. socket.on("connect_error", (err) => {
  9. console.log(err instanceof Error); // true
  10. console.log(err.message); // not authorized
  11. console.log(err.data); // { content: "Please retry later" }
  12. });

Compatibility with Express middleware

Most existing Express middleware modules should be compatible with Socket.IO, you just need a little wrapper function to make the method signatures match:

  1. const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);

The middleware functions that end the request-response cycle and do not call next() will not work though.

Example with express-session:

  1. const session = require("express-session");
  2. io.use(wrap(session({ secret: "cats" })));
  3. io.on("connection", (socket) => {
  4. const session = socket.request.session;
  5. });

Example with Passport:

  1. const session = require("express-session");
  2. const passport = require("passport");
  3. io.use(wrap(session({ secret: "cats" })));
  4. io.use(wrap(passport.initialize()));
  5. io.use(wrap(passport.session()));
  6. io.use((socket, next) => {
  7. if (socket.request.user) {
  8. next();
  9. } else {
  10. next(new Error("unauthorized"))
  11. }
  12. });

A complete example with Passport can be found here.