+8

Authentication cho socket.io

Về việc authentication, hẳn khi làm việc với HTTP đa phần đều đã được hỗ trợ rất kĩ với 2 kĩ thuật session và token. Hôm nay mình xin đề cập đến authentication cho giao thức socket và kĩ thuật mình muốn giới thiệu tới là JSON Web Tokens

JSON Web Tokens

JSON Web Tokens (JWT) là một chuẩn để mang thông tin dưới dạng json. Nó mang tất cả thông tin bên trong chính nó, đó là khả năng tự mô tả. Nó thường được pass vào HTTP header để authentication cho API Nó là một chuổi được chia thành 3 phần, được chia tách mới dấu dot:

aaaaaaaaaa.bbbbbbbbbbb.cccccccccccc

Bao gồm:

  • header
  • payload
  • signature

Ở đây:

  • Header có dạng:
{
  "typ": "JWT",
  "alg": "HS256"
}

mô tả loại và thuật toán để hash

  • Payload mang theo thông tin user, bất cứ data nào chúng ta muốn đính kèm cho quá trình auth
{
  "iss": "chatbot.dev",
  "exp": 1300819380,
  "name": "Phan Ngoc",
  "admin": true
}
  • Phần thứ 3 là secret, nó là thành phần được server mã hóa thông qua 1 secret key, được server dùng để verify token hiện tại hoặc để tạo 1 cái mới.

Các bước để auth.

Mình muốn giới thiệu một packet nổi tiếng cho việc verify vào tạo token trong nodejs là: https://github.com/auth0/node-jsonwebtoken Đây là ví dụ cho việc sinh token:

// sign with default (HMAC SHA256)
var jwt = require('jsonwebtoken');
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
//backdate a jwt 30 seconds
var older_token = jwt.sign({ foo: 'bar', iat: Math.floor(Date.now() / 1000) - 30 }, 'shhhhh');

// sign with RSA SHA256
var cert = fs.readFileSync('private.key');  // get private key
var token = jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256'});

Lưu ý là file private.key sẽ chứa secret key của chúng ta, nó chỉ được lưu trữ ở server. Để sinh ra token chúng ta chỉ cần:

// sign asynchronously
jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256' }, function(err, token) {
  console.log(token);
});

Để verify token chúng ta cần:

// verify a token symmetric
jwt.verify(token, 'secret key', function(err, decoded) {
  console.log(decoded.foo) // bar
});

Biến decoded sẽ mang theo thông tin payload của chúng ta.

Tiến hành auth cho socket.

Để auth cho giao thức socket, chúng ta tiến hành sinh token và gửi xuống client, khi client tiến hành handsocket với server, nó phải pass lại token đó để verify thông tin.

socket = io.connect('http://localhost', {
  query: "token=" + myAuthToken
});

Ở server chúng ta tiến hành get lại token, thông qua middleware mà socket io hỗ trợ:

io.use(function(socket, next) {
  var token = socket.request.query.token;
  checkAuthToken(token, function(err, authorized){
    if (err || !authorized) {
      next(new Error("not authorized"));
    }
    next();
  });
});

Tuy nhiên cách làm trên gặp một số vấn đề về security, đặc biệt là khi pass token trong query string. Ở đây chúng ta có thể cải thiện bằng cách cho user connect tới socket rồi sau đó mới emit token cho server auth, một khi user được auth chúng ta mới cho emit và listen sự kiện.

var io = require('socket.io').listen(app);
 
io.on('connection', function(socket){
  socket.auth = false;
  socket.on('authenticate', function(data){
    // check data được send tới client
    checkAuthToken(data.token, function(err, success){
      if (!err && success){
        console.log("Authenticated socket ", socket.id);
        socket.auth = true;
      }
    });
  });
 
  setTimeout(function(){
    //sau 1s mà client vẫn chưa dc auth, lúc đấy chúng ta mới disconnect.
    if (!socket.auth) {
      console.log("Disconnecting socket ", socket.id);
      socket.disconnect('unauthorized');
    }
  }, 1000);
}

Ở client chúng ta sẽ sửa thành:

var socket = io.connect('http://localhost');
socket.on('connect', function(){
  socket.emit('authenticate', {token: myAuthToken});
});

Chúng ta cần ngăn chặn client nhận broadcast messages khi được connect mà vẫn chưa được authentication:

var _ = require('underscore');
var io = require('socket.io').listen(app);
 
_.each(io.nsps, function(nsp){
  nsp.on('connect', function(socket){
    if (!socket.auth) {
      console.log("removing socket from", nsp.name)
      delete nsp.connected[socket.id];
    }
  });
});

Và khi authentication hoàn tất chúng ta sẽ gán socket id đó lại vào mảng socket namespace cần tracking cho việc nhận boardcast message hay listen event.

socket.on('authenticate', function(data){
  //check the auth data sent by the client
  checkAuthToken(data.token, function(err, success){
    if (!err && success){
      console.log("Authenticated socket ", socket.id);
      socket.auth = true;
 
      _.each(io.nsps, function(nsp) {
        if(_.findWhere(nsp.sockets, {id: socket.id})) {
          console.log("restoring socket to", nsp.name);
          nsp.connected[socket.id] = socket;
        }
      });
 
    }
  });
});

Hy vọng các bạn có cái nhìn tổng quan về authentication cho socket. Cảm ơn các bạn đã đọc !

Ref: https://phanngoc123.blogspot.com/2020/02/authentication-cho-socketio.html


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.