August 12, 2016

Xây dựng ứng dụng API với NodeJS - Bảo mật ứng dụng với JWT và Passport

source: https://scotch.io/wp-content/uploads/2014/04/restful-api-node-express-4-router.jpg
Ứng dụng xây xong mà không có bảo mật thì thật sự rất đáng nguy, ứng dụng nào cũng vậy chứ không riêng gì API. Hôm nay có tí chút thời gian, viết thêm bài viết về bảo mật ứng dụng bằng PassportJS và JWT. Để dễ hình dung, mình tóm tắt ngón gọn thế nào là PassportJS cũng như JWT:

Passport là một module trong NodeJS, rất linh động và tích hợp tương tác với nhiều kiểu chứng thực (authenticate) phổ biến như: Basic, Digest, OAuth, OAuth2 và dĩ nhiên là phải có JWT (không có sao có bài viết này được ^^). Còn JWT (Json Web Token) là một kiểu chứng thực (authenticate) đơn giản nhưng cũng an toàn dành cho REST API (rất hợp lý cho loạt bài này).

Rào trước đón sau: "Bài viết không có ý định làm rõ những khái niệm này, chỉ là một ghi chú của bản thân để sau này dễ triển khai." Nên nếu ai đó có sự thắc mắc muốn làm rõ hơn thì bạn nên tự thân vận động để nắm được vấn đề, có vậy bạn mới trở nên cao tay hơn.

Chuẩn bị

Để thực hiện bài này, chúng ta cần cài đặt những module sau:
  • passport: cái này là chắc chắn rồi. Đây là cơ chế xác thực, sau này muốn tích hợp chiến lược nào thì cứ việc tích hợp vào (và trong này chính là JWT)
  • passport-jwt: đây là chiến lược cần tích hợp với passport
  • jwt-simple: cái này dùng để mã hóa và giải mã token dạng JWT
npm install passport passport-jwt jwt-simple --save

Bắt tay vào code

Cấu hình chiến lược xác thực (authentication strategy)

Bắt tay vào, ta tạo mới một thư mục auth trong thư mục app, sau đó tạo tập tin index.js trong thư mục mới tạo này. Nhiệm vụ là khai báo, cấu hình chiến lược chứng thực, rồi báo cho thư viện passport biết.
var passport = require('passport'),
    passportJWT = require('passport-jwt'),
    config = require('../../config/config'),
    extractJwt = passportJWT.ExtractJwt,
    strategyJwt = passportJWT.Strategy;

var params = {
    secretOrKey: 'day_la_khoa_bi_mat',
    jwtFromRequest: extractJwt.fromAuthHeader()
};

var jwtStrategy = new strategyJwt(params, function (payload, done) {
    if (payload.id === 'BloBla') {
        return done(null, {id: payload.id});
    } else {
        return done(new Error("Not access"));
    }
});

passport.use('jwt', jwtStrategy);

module.exports.isJwtAuthenticated = passport.authenticate('jwt', {session: false});

Ở đây có 3 điều chúng ta cần lưu ý:

  • secretOfKey: đây là 1 chuỗi gì đó tùy ý, nhưng đừng public cho ai biết, vậy thôi
  • khai báo jwtStrategy: payload sẽ được truyền đi mỗi khi user gửi token lên cho server, passport sẽ tự phân rã nó ra theo cấu trúc mã hóa ban đầu. Ở đây, tạm thời các bạn cứ chấp nhận là có cái thứ <id> trong payload. Lát nữa bạn sẽ thấy nó tại sao lại xuất hiện trong đây.
  • payload.id === 'BloBla': chỗ này viết nhanh thôi, các bạn nên dùng 1 cơ chế khác (VD như truy xuất vào DB) để kiểm tra

Bây giờ, chúng ta cần thêm 1 controller, chịu trách nhiệm phát sinh token cho user, chúng ta sẽ đặt nó trong route dạng /auth/token. Để thực hiện điều này, chúng ta tạo thêm 1 tập tin mới, auth.js trong thư mục app/controllers với nội dung như sau:
var express = require('express'),
    router = express.Router(),
    jwt = require('jwt-simple'),
    config = require('../../config/config');

module.exports = function (app) {
    app.use('/auth', router);
};

router.post('/token', function (req, res, next) {
    var email = req.body.email || null,
        password = req.body.password || null;

    if (email !== 'tmquang6805@embedslide.net' || password !== 'TumLumtualua') {
        var error = new Error('Unauthorized');
        error.status = 401;
        return next(error);
    }

    var payload = {id: 'BloBla'};
    var token = jwt.encode(payload, 'day_la_khoa_bi_mat');
    res.json({token: token});
});

Ở đây, cũng có 2 điều bạn cần lưu ý:
  • chỗ email và password: cũng nên dùng một phương thức khác chứ không nên làm kiểu như blog. Chỉ là minh họa cho nhanh thôi
  • Khi response là chúng ta gửi về token cho user, token này được mã hóa dựa trên cái mã secret hồi nãy. Và giờ đây bạn hiểu được payload.id từ đâu mà có rồi đó

Bảo vệ những route dưới JWT

Để thực hiện điều này, rất đơn giản, đối với những route nào cần bảo mật JWT, thì chúng ta dùng phương thức isJwtAuthenticated mà đã thực hiện bên trện, dạng như sau:
var express = require('express'),
    router = express.Router(),
    auth = require('../auth');

module.exports = function (app) {
    app.use('/', router);
};

router.get('/', auth.isJwtAuthenticated, function (req, res, next) {
    return res.json({a: 123});
});

Vậy là xong, bây giờ chúng ta tiến hành thí nghiệm những gì đã qua.

Thí nghiệm

Lấy token
curl -XPOST http://localhost:3000/auth/token -d "email=tmquang6805@embedslide.net&password=TumLumtualua" 
Kết quả nhận về:
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IkJsb0JsYSJ9.DHdhrg51XK8yH2GCDQ7-sgFdlLVoYI_OhnU2B-ISffg"}
Sử dụng token đề cho các truy cập các route được bảo mật
curl -X GET -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IkJsb0JsYSJ9.DHdhrg51XK8yH2GCDQ7-sgFdlLVoYI_OhnU2B-ISffg"  "http://localhost:3000/"

Loạt bài xây dựng ứng dụng API với NodeJS

No comments:

Post a Comment