Authentication Nodejs with JWT

JSON Web Token là gì

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 1 đố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.

Trên đây là định nghĩa cơ bản về JWT, thực ra có nhiều bài viết nói về JWT nên trong bài viết này mình sẽ ko đi chi tiết , mà chỉ trình bày cách sử dụng nó để tạo Authentication trong ứng dụng đơn gian theo các step. Trong bài viết này mình sẽ sử dụng node js, mongodb, express....Và cách sử dụng các tools này thì mình sẽ ko đề cập mà mặc định là đã biết sư dụng 😃

Tạo cấu trúc ban đầu

Chạy câu lênh sau ở thư mục lưu project

npm install express-generator -g

Install jwt

npm install jsonwebtoken 

và install các dependencies khác như sau:

{
  "name": "AppName",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "bcrypt": "^1.0.3",
    "body-parser": "~1.17.1",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.3",
    "express": "~4.15.2",
    "jade": "~1.11.0",
    "jsonwebtoken": "^7.4.3",
    "mongoose": "^4.10.8",
    "morgan": "~1.8.1",
    "serve-favicon": "~2.4.2"
  }
}

Câu trúc thư mục cơ bản như sau:

Tạo user model

Trong thư mục models tạo file user.js Model user sẽ lưu thông tin của 1 account như username, password, email,... Password của user sẽ đc mã hoá và compare bằng cách sử dụng modul bcrypt user.js

//Require Mongoose
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');

//Define a schema
var UserSchema = new mongoose.Schema({
    username:{ type: String, unique: true, required: true },
    hash_password: { type: String, required: true},
    email: { type: String , lowercase: true, trim: true},
    created_date: String,
    first_name: String,
    last_name: String,
    birth_date: String,
    gender: String,
    avatar: String
}, {
    collection: 'user'
});

// comparePassword
UserSchema.methods.comparePassword = function (password) {
    return bcrypt.compareSync(password, this.hash_password)
}

//Export function to create "UserSchema" model class
module.exports = mongoose.model('User', UserSchema );

Trong thư mục Controller, tạo usercontroller. Trong controller này sẽ có 2 function như sau: usercontroller.js

var User = require('../models/user');
var config = require('../helpers/config');
var jwt = require('jsonwebtoken');
var bcrypt = require('bcrypt');

// register
exports.register = function(req, res) {

    // get user
    var user = new User(req.body);
    user.hash_password = bcrypt.hashSync(req.body.password, 10);

    // save
    user.save(function (err, newUser) {
        if (err) {
            res.json({"code": false, "message": "Error to save"});
            return
        }
        newUser.hash_password = undefined
        res.json({ message: 'Save ok', data: newUser });
    });
};

// login
exports.login = function(req, res) {

    var username = req.body.username;
    var password = req.body.password;

    // find
    User.findOne({
        'username': username
    }, function(err, user) {
        if (!user) {
            res.json({ error : 'User is not exist'})
        } else if (user &&
            user.comparePassword(password)) {
            var payload = { username: user.username };
            var jwtToken = jwt.sign(payload, config.jwtSecret, { expiresIn: 1 * 30 });
            console.log('jwtToken: ' + jwtToken);
            var jsonResponse = {'access_token': jwtToken, 'refresh_token': "xxxxx-xxx-xx-x"}
            res.json(jsonResponse)
        } else {
            res.json({ error : 'Login Error'})
        }
    })
};

Tạo route user (user.js) trong thư mục routes user.js

var express = require('express');
var router = express.Router();

var user_controller = require('../controllers/userController');
var auth_controller = require('../controllers/authController');

// Register
router.post('/register',  user_controller.register);

// Login
router.post('/login',  user_controller.login);

// Logout
router.get('/logout',  auth_controller.isAuthenticated, user_controller.logout);
module.exports = router;
  • Register: Lấy thông tin từ req và save vào database (Mongodb).Ở đây password sẽ được mã hoá bằng cách sử dụng bcrypt.hashSync(req.body.password, 10);
  • Login : Api này lấy thông tin login từ user (username, password).Nếu login thành công sẽ tạo jwtToken bằng cách sử dụng module "jsonwebtoken" .jwtToken sẽ được trả về cho user

authController

Trong controller này sẽ có hàm check xem api đã được chứng thực hay chưa authController.js

var User = require('../models/user');
var config = require('../helpers/config');
var jwt = require('jsonwebtoken');
var bcrypt = require('bcrypt');

exports.isAuthenticated = function(req, res, next) {
    if (req.headers &&
        req.headers.authorization &&
        req.headers.authorization.split(' ')[0] === 'JWT') {

        var jwtToken =  req.headers.authorization.split(' ')[1];
        jwt.verify(jwtToken, config.jwtSecret, function(err, payload) {

            if (err) {
                res.status(401).json({message: 'Unauthorized user!'});
            } else {
                console.log('decoder: ' + payload.username);
                // find
                User.findOne({
                    'username': payload.username
                }, function(err, user) {
                    if (user) {
                        req.user = user;
                        next();
                    } else {
                        res.status(401).json({ message: 'Unauthorized user!' });
                    }
                })
            }
        });
    } else {
        res.status(401).json({ message: 'Unauthorized user!' });
    }
};

Tất cả api đòi hỏi quyền access đều phải set header "authorization" = "JWT {jwttoken}".Và function này sẽ check và trả về 401 nếu api ko đc cấp quyền. Api nào cần check quyền sẽ được set trên route như ví dụ sau. users.js

var express = require('express');
var router = express.Router();

var users_controller = require('../controllers/usersController');
var auth_controller = require('../controllers/authController');

// GET list users
router.get('/', auth_controller.isAuthenticated, users_controller.users);
module.exports = router;

Đây là route users.js. Ở đây khi muốn lấy thông tin của tất cả user trên server, api phải được chứng thực. Đo đó, auth_controller.isAuthenticated sẽ check nếu được chứng thực thì sẽ gọi tiếp đến users_controller.users, còn không sẽ respone code 401 :'Unauthorized user!'

usersController.js

var User = require('../models/user');
// get current user
exports.users = function(req, res) {
    // find
    User.find({
    }, function(err, users) {
        if (err)
            throw err;
        if (users) {
            var jsonResponse = {'users': users}
            res.json(jsonResponse)
        } else {
            res.send(JSON.stringify({
                error : 'Login Error'
            }))
        }
    })
};

Others

dbController.js

var mongoose = require('mongoose');
var config = require('../helpers/config');
exports.connectMongoDb = function() {
    //connect to MongoDB
    var mongodbUri = config.dbUri
    var options = {
        server: { socketOptions: { keepAlive: 300000, connectTimeoutMS: 30000 } },
        replset: { socketOptions: { keepAlive: 300000, connectTimeoutMS: 30000 } }
    };
    mongoose.connect(mongodbUri, options);
};

configs.js

module.exports = {
    'jwtSecret': 'taodeptrai',
    'dbUri': 'mongodb://admin:[email protected]:xxxxx/appname'
};

Test

Register

curl -X POST \
  http://localhost:3000/api/v1/user/register \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'postman-token: 74debd36-ba43-a6b7-9e9e-4f16d4f4864d' \
  -d 'username=ltranframgia5&password=12345678'

Login

curl -X POST \
  http://localhost:3000/api/v1/user/login \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'postman-token: 7df0cb78-70ee-e786-a07c-7ebe3805e34d' \
  -d 'username=ltranframgia5&password=12345678'

Get Users

curl -X GET \
  http://localhost:3000/api/v1/users \
  -H 'authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imx0cmFuZnJhbWdpYTUiLCJpYXQiOjE1MDM5MTQ0OTMsImV4cCI6MTUwMzkxNDUyM30.VnTxYSX70HJmj7N7IDD_LRb3FicbkedRZ9CN_8MIBTI' \
  -H 'cache-control: no-cache' \
  -H 'postman-token: 216139be-2710-2747-c9c3-8ab3f3d86a30'