Xác thực trong NodeJS sử dụng JSON Web Token


1. Giới thiệu JSON web token
Xác thực dựa trên token nổi bật khắp mọi nơi trên ứng dụng web ngày nay. Với hầu hết mọi ứng dụng web sử dụng API, token là cách tốt nhất để xử lý xác thực cho nhiều người dùng.
Xác thực dựa trên token là stateless: không lưu trữ bất kỳ thông tin nào về user trên server hoặc trong session.
JSON Web Token (JWT) là 1 tiêu chuẩn mở (RFC 7519) định nghĩa cách thức truyền tin an toàn giữa các thành viên bằng một đối tượng JSON. Thông tin này có thể được xác thực và đánh dấu tin cậy nhờ vào "chữ ký" của nó. Phần chữ ký của JWT sẽ được mã hóa lại bằng HMAC hoặc RSA.
JSON Web Token bao gồm 3 phần, được ngăn cách nhau bởi dấu chấm (.):
  • Header
  • Payload
  • Signature
2. Xây dựng ứng dụng xác thực sử dụng JSON web token
Chúng ta sẽ xây dựng một API sử dụng Nodejs và Express.
Workflow chính gồm:
  • Ứng dụng có các routes yêu cầu xác thực và không yêu cầu xác thực
  • User sẽ xác thực bằng cách gửi username, password và nhận về token
  • User lưu trữ token phía client-side and gửi nó đối với mỗi request
  • Server sẽ validate token, nếu token hợp lệ sẽ trả lại thông tin theo JSON format
API sẽ được xây dựng gồm các route:
  • Normal routes(không yêu cầu xác thực)
  • Route middleware để xác thực token
  • Route để xác thực một user và password và lấy token
  • Authenticated routes để lấy toàn bộ user
Cấu trúc thư mục:
- app/
----- models/
---------- user.js
- config.js
- package.json
- server.js
Cài đặt package yêu cầu trong ứng dụng trong package.jon file
{
  "name": "JSON web token demo",
  "version": "0.0.0",
  "dependencies": {
    "body-parser": "~1.16.0",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.0",
    "dotenv": "^4.0.0",
    "express": "~4.14.1",
    "jsonwebtoken": "^7.3.0",
    "mongoose": "^4.8.3",
    "morgan": "~1.7.0"
  }
}
Khởi tạo model User: sử dụng khi tạo và lấy thông tin user.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

module.exports = mongoose.model('User', new Schema({
  name: String,
  password: String,
  admin: Boolean
}));
Tạo config.js file để lưu trữ biến và config ứng dụng
module.exports = {
  secret: 'jsonwebtoken',
  url : 'mongodb://localhost/'
};
  • secret: được sử dụng khi tạo và verify JSON web token
  • url: uri kết nối tới mongodb
Tạo API router bao gồm:
Tạo file server.js
var express     = require('express');
var app         = express();
var bodyParser  = require('body-parser');
var morgan      = require('morgan');
var mongoose    = require('mongoose');
var jwt    = require('jsonwebtoken'); 
var config = require('./config'); 
var User   = require('./app/models/user'); 

var port = process.env.PORT || 8080; 
mongoose.connect(config.database); 
app.set('superSecret', config.secret); 

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(morgan('dev'));

app.get('/', function(req, res) {
  res.send('The API is at http://localhost:' + port + '/api');
});

app.listen(port);
Bây giờ chúng ta đã có thể start Node server
$ node server.js
$ curl http://localhost:8080
The API is at http://localhost:8080/api
Tạo user sử dụng User model
app.get('/setup', function(req, res) {
  var nick = new User({ 
    name: 'Nick Cerminara', 
    password: 'password',
    admin: true 
  });

  nick.save(function(err) {
    if (err) throw err;
    console.log('User saved successfully');
    res.json({ success: true });
  });
});
$ curl http://localhost:8080/setup
{"success":true}
Show thông tin user
var apiRoutes = express.Router(); 

apiRoutes.get('/', function(req, res) {
  res.json({ message: 'Welcome to API' });
});

apiRoutes.get('/users', function(req, res) {
  User.find({}, function(err, users) {
    res.json(users);
  });
});   

app.use('/api', apiRoutes);
Xác thực vào tạo token
var apiRoutes = express.Router(); 

apiRoutes.post('/authenticate', function(req, res) {
  User.findOne({
    name: req.body.name
  }, function(err, user) {
    if (err) throw err;
    if (!user) {
      res.json({ success: false, message: 'Authentication failed. User not found.' });
    } else if (user) {
      if (user.password != req.body.password) {
        res.json({ success: false, message: 'Authentication failed. Wrong password.' });
      } else {
        var token = jwt.sign(user, app.get('superSecret'), {
          expiresInMinutes: 1440 
        });
        res.json({
          success: true,
          message: 'Enjoy your token!',
          token: token
        });
      }   
    }
  });
});
$ curl -X POST http://localhost:8080/api/authenticate
{"success":false,"message":"Authentication failed. User not found."}
Route middleware
Chúng ta đã định nghĩa 3 routes:
  • /api/authenticate
  • /api
  • /api/users
Bây giờ chúng ta sẽ đi vào tạo route middleware để bảo vệ 2 routes yêu cầu xác thực.
var apiRoutes = express.Router(); 

apiRoutes.use(function(req, res, next) {
  var token = req.body.token || req.query.token || req.headers['x-access-token'];
  if (token) {
    jwt.verify(token, app.get('superSecret'), function(err, decoded) {      
      if (err) {
        return res.json({ success: false, message: 'Failed to authenticate token.' });    
      } else {
        req.decoded = decoded;    
        next();
      }
    });
  } else {
    return res.status(403).send({ 
      success: false, 
      message: 'No token provided.' 
    });
  }
});

app.use('/api', apiRoutes);
Chúng ta sử dụng jsonwebtoken package để kiểm tra token đã đựợc pass vào dựa trên secret đã được config. Chúng ta cũng tạo HTTP respone với code 403 và user không đựợc xác thực để xem bất kỳ dữ liệu nào.
Nếu token là hợp lệ thì request sẽ được chuyển đến routes khác qua req object
$ curl http://localhost:8080/api/users?token=eyJ0eXAiOiJ...Yw

[{"_id":"58d93b517a65e643b25ce0f3","
name":"Nick Cerminara",
"password":"password",
"admin":true,
"__v":0}]
Tài liệu tham khảo:

Comments

Popular posts from this blog

So sánh giữa Node.js và Golang

Node.js Tutorial: Phần 7 - Sử dụng EJS làm Template Engine trong Express