JSON Web Token (JWT) là gì ? Xác thực Nodejs với JWT

Với bài viết này, mình sẽ tìm hiểu về JWT là gì ? và hướng dẫn các bạn các bước để chúng ta tích hợp xác thực JWT vào project hiện tại.6 min


1060
1.6k shares, 1060 points

JSON Web Token là gì?

JSON Web Token (JWT) là một chuẩn mở (RFC 7519) định nghĩa một cách nhỏ gọn và khép kín để truyền một cách an toàn thông tin giữa các bên dưới dạng đối tượng JSON. Thông tin này có thể được xác minh và đáng tin cậy vì nó có chứa chữ ký số. JWTs có thể được ký bằng một thuật toán bí mật (với thuật toán HMAC) hoặc một public / private key sử dụng mã hoá RSA.

Một ví dụ về JWT Token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.
uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo

Thoạt trông phức tạp là thế nhưng nếu hiểu, cấu trúc của một JWT chỉ đơn giản như sau:

<base64-encoded header>.
<base64-encoded payload>.
<base64-encoded signature>

Nói một cách khác, JWT là sự kết hợp (bởi dấu .) một Object Header dưới định dạng JSON được encode base64, một payload object dưới định dạng JSOn được encode base64 và một Signature cho URI cũng được mã hóa base64 nốt.

Giải thích thêm về 3 thành phần của JWT

Header

Header bao gồm hai phần chính: loại token (mặc định là JWT – Thông tin này cho biết đây là một Token JWT) và thuật toán đã dùng để mã hóa (HMAC SHA256 – HS256 hoặc RSA).

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

Payload chứa các claims. Claims là một các biểu thức về một thực thể (chẳng hạn user) và một số metadata phụ trợ. Có 3 loại claims thường gặp trong Payload: reserved, public và private claims.

Reserved claims: Đây là một số metadata được định nghĩa trước, trong đó một số metadata là bắt buộc, số còn lại nên tuân theo để JWT hợp lệ và đầy đủ thông tin: iss (issuer), iat (issued-at time) exp (expiration time), sub (subject), aud (audience), jti (Unique Identifier cho JWT, Can be used to prevent the JWT from being replayed. This is helpful for a one time use token.) … Ví dụ:

{
    "iss": "jira:1314039",
    "iat": 1300819370,
    "exp": 1300819380,
    "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9",
    "sub": "batman",
    "context": {
        "user": {
            "userKey": "batman",
            "username": "bwayne",
            "displayName": "Bruce Wayne"
        }
    }
}
{
  "iss": "scotch.io",
  "exp": 1300819380,
  "name": "Chris Sevilleja",
  "admin": true
}

Public Claims – Claims được cộng đồng công nhận và sử dụng rộng rãi.

Private Claims – Claims tự định nghĩa (không được trùng với Reserved Claims và Public Claims), được tạo ra để chia sẻ thông tin giữa 2 parties đã thỏa thuận và thống nhất trước đó.

Signature

Chữ ký Signature trong JWT là một chuỗi được mã hóa bởi header, payload cùng với một chuỗi bí mật theo nguyên tắc sau:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Do bản thân Signature đã bao gồm cả header và payload nên Signature có thể dùng để kiểm tra tính toàn vẹn của dữ liệu khi truyền tải.

Xác thực Nodejs với JWT

Cài đặt JWT package

jsonwebtoken là package của Node phát triển dựa trên draft-ietf-jose-json-web-signature-08

Tạo user model

Trong folder api/models folder, tạo một file userModel.js. Nhưng các bạn đã biết, MongoDB cho phép chúng ta tạo một schema và là nơi chúng ta có thể tạo documents. Chúng ta sẽ sử dụng nó để tạo user trực tiếp trong User document. Trong bài viết này chúng ta sử dụng package mongoose của nodejs để tạo schema với các thuộc tính như sau:

  • fullName
  • email address
  • passwork
  • created (ngày tạo)
'use strict';
var mongoose = require('mongoose'),
  bcrypt = require('bcrypt'),
  Schema = mongoose.Schema;
//User Schema
var UserSchema = new Schema({
  fullName: {
    type: String,
    trim: true,
    required: true
  },
  email: {
    type: String,
    unique: true,
    lowercase: true,
    trim: true,
    required: true
  },
  hash_password: {
    type: String,
    required: true
  },
  created: {
    type: Date,
    default: Date.now
  }
});
UserSchema.methods.comparePassword = function(password) {
  return bcrypt.compareSync(password, this.hash_password);
};
mongoose.model('User', UserSchema);

Tạo phương thức cho user (sign in, register and yêu cầu login)

Trong thư mục api/controllers tạo file userController.js Trong file userController chúng ta tạo export 3 thương thức khác nhau với NodeJS

'use strict';
var mongoose = require('mongoose'),
  jwt = require('jsonwebtoken'),
  bcrypt = require('bcrypt'),
  User = mongoose.model('User');
exports.register = function(req, res) {
};
exports.sign_in = function(req, res) {
};
exports.loginRequired = function(req, res, next) {
};

Trong phương thức đăng kí, chúng ta tạo instance user model với User Schema và lưu trong MongoDB

exports.register = function(req, res) {
  var newUser = new User(req.body);
  newUser.hash_password = bcrypt.hashSync(req.body.password, 10);
  newUser.save(function(err, user) {
    if (err) {
      return res.status(400).send({
        message: err
      });
    } else {
      user.hash_password = undefined;
      return res.json(user);
    }
  });
};

Lưu ý: Password lưu xuống database phải bcrypt bằng cách sử dụng Bcrypt package của nodejs

Trong phương thức sign_in chúng ta thực hiện việc hoạt động login. Đầu tiên, chúng ta kiểm tra user đó có tồn tại trong database hay không thông qua email(primary). Nếu có chúng ta kiểm tra password mới phương thức comparePassword trong user model user. Nếu thành công response json với tham số email, fullName, id (những tham số này đã được mã hóa khi truyền đến clients). Nếu không match thì response trả lỗi về client với NodeJS

exports.sign_in = function(req, res) {
  User.findOne({
    email: req.body.email
  }, function(err, user) {
    if (err) throw err;
    if (!user) {
      res.status(401).json({ message: 'Authentication failed. User not found.' });
    } else if (user) {
      if (!user.comparePassword(req.body.password)) {
        res.status(401).json({ message: 'Authentication failed. Wrong password.' });
      } else {
        return res.json({token: jwt.sign({ email: user.email, fullName: user.fullName, _id: user._id}, 'RESTFULAPIs')});
      }
    }
  });
};

Trong phương thúc loginRequired middleware kiểm tra user đã login hay là chưa. Phương thức này sẽ chạy đầu tiên thành công next() sẽ chuyển hướng đến các hoạt động tiếp theo

exports.loginRequired = function(req, res, next) {
  if (req.user) {
    next();
  } else {
    return res.status(401).json({ message: 'Unauthorized user!' });
  }
};

Tạo user routes NodeJS

Trong folder api/routes tạo file todoListRoutes.js. Đây là router cho user register, signup vào sign in để trong cập trên ứng dụng web của chúng ta

'use strict';
module.exports = function(app) {
   var userHandlers = require('../controllers/userController.js');
   app.route('/auth/register')
   	.post(userHandlers.register);
   app.route('/auth/sign_in')
   	.post(userHandlers.sign_in);
};

Updating server.js file Nodejs

Chúng ta tạo file server.js. File này chúng ta tạo instanse User và thiết lập middleware cho express server để check trạng thái của user.

'use strict';
var express = require('express'),
  app = express(),
  port = process.env.PORT || 3000,
  mongoose = require('mongoose'),
  Task = require('./api/models/todoListModel'),
  User = require('./api/models/userModel'),
  bodyParser = require('body-parser'),
  jsonwebtoken = require("jsonwebtoken");
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/Tododb');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

Sau đó chúng ta tạo middleware cho express, phải chắc chắn rằng middleware này chạy đầu tiên

app.use(function(req, res, next) {
  if (req.headers && req.headers.authorization && req.headers.authorization.split(' ')[0] === 'JWT') {
    jsonwebtoken.verify(req.headers.authorization.split(' ')[1], 'RESTFULAPIs', function(err, decode) {
      if (err) req.user = undefined;
      req.user = decode;
      next();
    });
  } else {
    req.user = undefined;
    next();
  }
});

Như vậy chúng ta đã tạo server, model, controller đã xong. Bây giờ chúng ta init server và kết hợp với postman để test API chúng ta viết ra

npm run start

Đây là những thứ tự hình ảnh mà chúng ta test với postman

Kết luận

Như vậy chúng ta đã hiểu về JWT là gì ? Và cách sử dụng JWT kết hợp với NodeJS. Mong bạn có thể áp dụng vào project, app của bản thân.

Tìm hiểu thêm về xác thực NodeJS : Authentication trong Nodejs là gì ? Tại sao quan trọng ?


Like it? Share with your friends!

1060
1.6k shares, 1060 points

What's Your Reaction?

hate hate
0
hate
confused confused
0
confused
fail fail
0
fail
fun fun
0
fun
geeky geeky
0
geeky
love love
0
love
lol lol
1
lol
omg omg
0
omg
win win
2
win