59

So I have an application running node js with socket.io as a backend and normal javascript as frontend. My application has a login system which currently simply has the client send its login data as soon as it's connected.

Now I figured it would be much nicer to have the login data sent along with the handshakeData, so I can directly have the user logged in while connecting (instead of after establishing a connection) respectively refuse authorization when the login data is invalid.

I'm thinking it would be best to put my additional data in the header part of the handshakeData, so any ideas how I could do that? (Without having to modify socket.io if possible, but if it's the only way I can live with it)

Wingblade
  • 8,065
  • 9
  • 30
  • 41

7 Answers7

144

As a lot of comments have pointed out below the Socket.IO API changed in their 1.0 release. Authentication should now be done via a middleware function, see 'Authentication differences' @ http://socket.io/docs/migrating-from-0-9/#authentication-differences. I'll include my orginal answer for anyone stuck on <1.0 as the old docs seem to be gone.

1.0 and later:

Client Side:

//The query member of the options object is passed to the server on connection and parsed as a CGI style Querystring.
var socket = io("http://127.0.0.1:3000/", { query: "foo=bar" });

Server Side:

io.use(function(socket, next){
    console.log("Query: ", socket.handshake.query);
    // return the result of next() to accept the connection.
    if (socket.handshake.query.foo == "bar") {
        return next();
    }
    // call next() with an Error if you need to reject the connection.
    next(new Error('Authentication error'));
});

Pre 1.0

You can pass a query: param in the second argument to connect() on the client side which will be available on the server in the authorization method.

I've just been testing it. On the client I have:

var c = io.connect('http://127.0.0.1:3000/', { query: "foo=bar" });

On the server:

io.set('authorization', function (handshakeData, cb) {
    console.log('Auth: ', handshakeData.query);
    cb(null, true);
});

The output on the server then looked like:

:!node node_app/main.js
   info  - socket.io started
Auth:  { foo: 'bar', t: '1355859917678' }

Update

3.x and later

You can pass an authentication payload using the auth param as the second argument to connect() in the client side.

Client Side:

io.connect("http://127.0.0.1:3000/", {
    auth: {
      token: "AuthToken",
    },
  }),

In server side you can access it using socket.handshake.auth.token

Server Side:

io.use(function(socket, next){
    console.log(socket.handshake.auth.token)
    next()
});
albinjose
  • 63
  • 8
Alex Wright
  • 1,711
  • 1
  • 12
  • 13
  • 3
    Thanks! Exactly what I wanted! +1 – Wingblade Dec 20 '12 at 09:34
  • Is it possible to also send client some data back with a handshake? – Rob Fox May 21 '13 at 11:50
  • @RobFox IIRC Rob I don't think it is, but you can write to the channel immediately after it's created and that will be the first thing the connecting client would see. – Alex Wright May 21 '13 at 15:39
  • 2
    Excellent. This helped me out immensely. Thank you. – Art Geigel Aug 27 '13 at 05:25
  • It's worth noting that this implies sending the data as a querystring, which is not advisable if that data is supposed to be secret (i.e. passwords or authorization tokens). – Facundo Olano Oct 11 '14 at 01:28
  • 3
    @FacundoOlano, how would one acchieve this differently? Regardless of querystring, postvars or as a special header, the token is sniffable if no https is used. But I'd really like to know your solution to mitigate such a MIM attack – japrescott Jan 06 '15 at 16:08
  • 2
    @japrescott you can't expect any security without https; but even with https, sending tokens in the querystring is not advisable. My workaround is sending an authentication message and putting the token in the message body. I've explained it [here](https://facundoolano.wordpress.com/2014/10/11/better-authentication-for-socket-io-no-query-strings/) – Facundo Olano Jan 27 '15 at 17:29
  • @AlexWright: How can I pass 2 parameters with query option? Thanks in advance.! – Pritam Mar 18 '15 at 06:39
  • @Pritam You'd just append the second parameter to the querystring (in the example `foo=bar`). So an example with two parameters could be `foo=bar&baz=whatever`. Consider using `encodeURIComponent()` to encode the value if it has anything that's not legal or expected in a URL. – Alex Wright Mar 18 '15 at 11:07
  • @AlexWright your answer doens't work with newer socket.io versions, could you update it to reflect my answer (http://stackoverflow.com/a/27605655/2866570) – zaynetro May 07 '15 at 12:29
  • The `.set()` method is [deprecated](http://socket.io/docs/migrating-from-0-9/#authentication-differences) – Ravi May 23 '15 at 23:41
  • Its also worth noting that you can use an object instead of an encoded string. eg: instead of `{ query: "foo=bar" }` you can do `{ query: { foo: "bar" } }` – Deminetix Dec 09 '15 at 00:26
  • @Deminetix Is this maybe only since the API change? I tried that on the pre-1.0 instance and it broke trying to `.split` on an `&`. – Alex Wright Dec 09 '15 at 10:49
  • 1
    @AlexWright Yes this is with the latest, installed yesterday – Deminetix Dec 09 '15 at 23:24
19

This has now been changed in v1.0.0. See the migration docs

basically,

io.set('authorization', function (handshakeData, callback) {
  // make sure the handshake data looks good
  callback(null, true); // error first, 'authorized' boolean second 
});

becomes :

  io.use(function(socket, next) {
  var handshakeData = socket.request;
  // make sure the handshake data looks good as before
  // if error do this:
    // next(new Error('not authorized');
  // else just call next
  next();
});
Emre
  • 677
  • 9
  • 12
slupek2013
  • 549
  • 5
  • 9
10

For socket.io v1.2.1 use this:

io.use(function (socket, next) {
  var handshake = socket.handshake;
  console.log(handshake.query);
  next();
});
zaynetro
  • 2,211
  • 18
  • 26
7

This my code for sending query data to nodejs and server.io server client.

var socket = io.connect(window.location.origin, { query: 'loggeduser=user1' });

io.sockets.on('connection', function (socket) {
    var endp = socket.manager.handshaken[socket.id].address;
    console.log("query... " + socket.manager.handshaken[socket.id].query.user);
}
toriningen
  • 6,341
  • 2
  • 35
  • 59
user2969994
  • 71
  • 1
  • 1
  • This was the best solution. But `socket.manager.handshaken[socket.id].query.user` should be `socket.manager.handshaken[socket.id].query.loggeduser` – Valter Lorran Jul 16 '14 at 13:48
  • 4
    This probably is version dependent. I could only get it to work with socket.handshake.query.userOrWhatever – makc Sep 13 '14 at 14:33
1

Perhaps the api has changed but I did the following to get extra info to the server.

// client
io.connect('localhost:8080', { query: 'foo=bar', extra: 'extra'});

// server
io.use(function(sock, next) {
  var handshakeData = sock.request;
  console.log('_query:', handshakeData._query);
  console.log('extra:', handshakeData.extra);
  next();
});

prints

_query: { foo: 'bar',
  EIO: '3',
  transport: 'polling',
  t: '1424932455409-0' }
extra: undefined

If anyone knows how to get data from a client to the server through the handshake that is not in the query params let me know please.

Update I ran into issues later with this syntax

io.connect('localhost:8080?foo=bar');

is what I'm currently using.

Harry Moreno
  • 7,937
  • 3
  • 49
  • 89
1

Old thread but assuming you store your jwt token/session id in session cookies (standard stuff) this gets passed to the server by default anyway when doing handshake (socket.io-client) I've noticed. Is there anything wrong with just getting the auth information for the handshake (via middleware or on.connection) via cookie? eg.

io.on('connection', function(socket) {
  // assuming base64url token
  const cookieStr = socket.handshake.headers.cookie
  const matchRes =
    cookieStr == null
      ? false
      : cookieStr.match(/my-auth-token=([a-zA-Z0-9_.-]+)/)
  if (matchRes) {
    // verify your jwt...
    if ( tokenIsGood(matchRes[1]) {
      // handle authenticated new socket
    } else {
      socket.emit('AUTH_ERR_LOGOUT')
      socket.disconnect()
    }
  } else {
    socket.emit('AUTH_ERR_LOGOUT')
    socket.disconnect()
  }
}

I'm using this now for a project and it's working fine.

KM3
  • 414
  • 4
  • 2
0

I found a little problem to see the .loggeduser

io.sockets.on('connection', function (socket) {
    var endp = socket.manager.handshaken[socket.id].address;
    console.log("query... " + socket.manager.handshaken[socket.id].query.loggeduser);
                                                                         // ↑ here
}
Puffin GDI
  • 1,624
  • 4
  • 27
  • 34