Hôm nay mình sẽ hướng dẫn các bạn xây dựng API để đăng ký đăng nhập với nodejs, express, mongodb và jwt.
Xây dựng api login register với nodejs xác thực Với JWT 2021
- Trang Chủ
- [NODEJS CƠ BẢN] platform nodejs - V8 JavaScript Runtime
- Xây dựng api login register với nodejs xác thực Với JWT 2021
JWT 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 thông tin một cách an toàn giữa các bên dưới dạng đối tượng JSON.
Tóm lại ngắn gọn sẽ là jwt là cái Auth0 để login 😄 chỉ thế thôi.
Cài cắm nodejs npm thì chắc chắn bạn phải biết rồi, nếu bạn nào chứ biết start 1 nodejs nhanh với express thì xem thử qua bài này : Tạo 1 app nodejs đầu tiên sử dụng Express application generator 2021. Bạn nào còn chưa biết tạo kết nối đến mongodb thông qua mongoose thì xem bài này : Kết nối đến MongoDB với Nodejs dùng mongoose
Cài đặt packagejsonwebtoken
Vì chủ đề hôm nay là chúng ta cần làm việc với jwt nên việc cài đặt package cho việc giải mã và tạo ra jwt thông qua 1 thư viện là điều cần thiết
npm i jsonwebtoken
Để validate mình dùng cài 1 package có kiểu viết na ná jquery để dễ maintain chứ không dùng validate của nodejs hay dùng:
npm i node-input-validator
Để băm password thì mình phải dùng 1 hàm băm và thường nodejs sẽ dùng :
npm i bcrypt
Mình có tạo 1 file .env nên sẽ cần package dotenv
npm i dotenv
Tạo thư mụccontroller ngang cấp với app.js
Chúng ta cần tạo controller để làm việc với api. Mình hay tổ chức thư mục kiểu:
|- PROJECT/ | |- controller/ | |- |- Api/ | |- |- |- user.controller.js | |- middleware/ | |- |-
general.middleware.js| |- |-
jwt.middleware.js| |- |- user
.middleware.js| |- helper/ | |- |- auth.helper.js | |- |- error.helper.js | |- models/ | |- |- user.model.js | |- routes/ | |- |- api.js | |- .env | |- app.js | |- package.json
File auth.helper.js
Dùng để định nghĩa phương thức xác tạo jwt và xác thực jwt trong mỗi request. Code file auth.helper.js
như sau:
require('dotenv').config()
const jwt = require('jsonwebtoken')
const secret = process.env.JWT_SECRET || 'jsonwebtoken-secret' // mình có dùng dotenv
// vào file .env tạo 1 chuỗi JWT_SECRET bất kỳ
let hashTokenAccess = async user => {
return jwt.sign(user, secret, { expiresIn: '1800s' })
}
module.exports = {
hashTokenAccess
}
error.helper.js
module.exports.apiResponseErrorResource = function( req, res ){
let response = {}
if(req.errors){
let errors = Object.keys(req.errors).map( objKey => {
let error = req.errors[objKey];
return { error: objKey, ...error }
})
response.code = 422,
response.message = "đã có lỗi xảy ra"
response.internal_message = "đã có lỗi xảy ra"
response.errors = errors
return res.status(response.code).json(response)
}
}
user.model.js:
'use strict'
const bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 12,
mongoose = require('mongoose'),
Schema = mongoose.Schema
const UserSchema = new Schema(
{
username: {
type: String,
required: true,
minlength: 1,
maxlength: 3000
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
minlength: 1,
maxlength: 500
},
password: {
type: String,
required: true,
maxlength: 100000,
}
}, {
timestamps: true
}
)
/**
* Là function để so sánh chuỗi hash password đã gửi vào có đúng với trên hệ thống không?
* @param { String } _password là password đang được hash
* @version 0.0.1
*/
UserSchema.methods.comparePassword = function(_password) {
return bcrypt.compareSync(_password, this.password);
}
/**
* Là function để trước khi lưu mới hoặc update mới password thì sẽ modify nó trước khi lưu
* @async
* @version 0.0.1
*/
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next()
/// nếu là thêm mới hoặc update password thì băm trước
try {
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR)
this.password = await bcrypt.hash(this.password, salt)
return next()
} catch (err) {
return next(err)
}
})
UserSchema.methods.toResources = function() {
return {
_id : this._id,
username : this.username,
email : this.email,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
}
}
module.exports = mongoose.model("users", UserSchema)
file user.controller.js như sau:
const User = require("../../models/user.model"),
authHelper = require("../../helpers/auth.helper")
/**
* Là function với method post để đăng ký 1 user vào hệ thống
* @async
* @method POST
* @param {{ body: Object }} req
* @param { Object } res
* @returns { Promise<json> } là 1 json object trả về đối tượng user vừa đăng ký
* @version 0.0.1
* @see https://... đường đẫn đến swagger
*/
module.exports.register = async ( req, res ) => {
let code = 500,
response = {}
/// giả sử khúc này tới đây bạn đã sử dụng middleware ở ngoài để validate dữ liệu đầu vào
try {
const { username, email, password } = req.body
/// check email tồn tại
const isExist = await User.findOne({ email })
if( isExist ){
code = 409 /// 409 Conflict
throw new Error("email đã tồn tại!!")
}
/// lưu vào db mongo
const user = await new User({ username, email, password }).save()
/// khúc này nếu bạn kỹ tính hãy tạo 1 phương thức chung để format dữ liệu
/// còn mình làm nhanh thì trả ra dữ liệu luôn
response.code = 200
response.data = user.toResources()
response.message = "tạo user thành công"
response.internal_message = `Bạn đã tạo thành công mới 1 user với email là ${email}`
return res.status(response.code).json(response)
} catch (error) {
let err = { error: 'error', message: error.message }
response.code = code || 500
response.message = error.message
response.internal_message = error.message
response.errors = [ err ]
return res.status(response.code).json(response)
}
}
/**
* Là function với method post để login 1 user vào hệ thống
* @async
* @method POST
* @param {{ body: Object }} req
* @param { Object } res
* @returns { Promise< JSON > } là 1 json object trả về jwt
* @version 0.0.1
* @see https://... đường đẫn đến swagger
*/
module.exports.login = async ( req, res ) => {
let code = 500,
response = {}
/// giả sử khúc này tới đây bạn đã sử dụng middleware ở ngoài để validate dữ liệu đầu vào
try {
const { email, password } = req.body
/// check email tồn tại
const user = await User.findOne({ email }) /// vì email duy nhất nên findOne
if( !user ){
code = 401 /// 401 Unauthorized
throw new Error("Email hoặc password không đúng!!")
}
/// nếu có email cần check xem password có match đúng với db không?
const isMatch = await user.comparePassword(password)
if( !isMatch ){
/// nếu password không chính xác thì trả ra lỗi
code = 401 /// 401 Unauthorized
throw new Error("Email hoặc password không đúng!!")
}
/// nếu email và password chính xác thì tạo 1 mã jwt quăng ra cho ngừoi dùng
const strJWT = await authHelper.hashTokenAccess(user.toResources())
/// khúc này nếu bạn kỹ tính hãy tạo 1 phương thức chung để format dữ liệu
/// còn mình làm nhanh thì trả ra dữ liệu luôn
response.code = 200
response.data = strJWT
response.message = "User login thành công"
response.internal_message = `Bạn đã login thành công mới 1 user với email là ${email}`
return res.status(response.code).json(response)
} catch (error) {
let err = { error: 'error', message: error.message }
response.code = code || 500
response.message = error.message
response.internal_message = error.message
response.errors = [ err ]
return res.status(response.code).json(response)
}
}
module.exports.getUser = async (req, res) => {
let response = {},
code = 500
try {
const { user } = req
/// response
response.code = 200
response.data = user
response.message = "buộc phải login nè"
response.internal_message = "buộc phải login nè"
return res.status(response.code).json(response)
} catch (error) {
let err = { error: 'error', message: error.message }
response.code = code || 500
response.message = error.message
response.internal_message = error.message
response.errors = [err]
return res.status(response.code).json(response)
}
}
//
Trước khi chúng ta define các router thì chúng ta nhìn qua vài middleware mình có dùng đến :
general.middleware.js
let setAllowOrigin = (req, res, next) => {
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
console.log("================================================")
console.log( Date.now() + 'Request URL:', req.originalUrl)
console.log("================================================")
next()
}
let formatJsonApi = ( req, res, next ) => {
res.setHeader('Content-Type', 'application/json')
next()
}
module.exports = {
formatJsonApi,
setAllowOrigin,
}
jwt.middleware.js
require('dotenv').config()
const jwt = require('jsonwebtoken')
const secret = process.env.JWT_SECRET || 'jsonwebtoken-secret' // mình có dùng dotenv
let isAuth = async (req, res, next) => {
let code = 401,
response = {}
// Lấy token được gửi lên từ phía client, thông thường tốt nhất là các bạn nên truyền token vào header
const access = req.headers["x-access-token"] || req.headers["authorization"] || req.query.token || req.body.token
try {
if (!access) {
code = 403
/// không tồn tại access token
throw new Error('Unauthorized!!!')
}
// Thực hiện giải mã token xem có hợp lệ hay không?
// let user = await jwt.decode( access, secret ) => hàm này chỉ decode thôi nghen không phải xác thực
const user = await jwt.verify( access, secret ) // hàm này để xác thực
// Nếu token hợp lệ, lưu thông tin giải mã được vào đối tượng req, dùng cho các xử lý ở phía sau.
req.user = user
// Cho phép req đi tiếp sang controller.
next();
} catch (error) {
// Nếu giải mã gặp lỗi: Không đúng, hết hạn...etc:
response.code = code || 401
response.message = error.message || 'Unauthorized.'
response.internal_message = error.message || 'Unauthorized!'
return res.status(response.code).json(response)
}
}
let isAuthSocket = async token => {
try {
return await jwt.verify( token, secret )
} catch (error) {
return false
}
}
module.exports = {
isAuth,
isAuthSocket,
}
user
.middleware.js
const node_validator = require('node-input-validator'),
errorHelper = require('../helpers/error.helper'),
{ Validator } = node_validator
let REGISTER = async function( req, res, next ){
let validate = new Validator(req.body, {
username: "required|string|minLength:1|maxLength:10000",
email : "required|email|minLength:3|maxLength:500",
password: "required|string|minLength:1|maxLength:1000",
},{
'title.required' : ":attribute is required"
});
let matched = await validate.check()
if (!matched) {
req.errors = validate.errors
return errorHelper.apiResponseErrorResource( req, res )
}
next()
}
let LOGIN = async function( req, res, next ){
let validate = new Validator(req.body, {
email : "required|email|minLength:3|maxLength:500",
password: "required|string|minLength:1|maxLength:1000",
},{
'title.required' : ":attribute is required"
});
let matched = await validate.check()
if (!matched) {
req.errors = validate.errors
return errorHelper.apiResponseErrorResource( req, res )
}
next()
}
module.exports = {
REGISTER,
LOGIN,
}
Cuối cùng file routers/api.js sẽ như sau :
/**
* author hungtt
*/
const express = require("express")
const router = express.Router()
const userApiController = require("../controller/Api/user.controller")
const generalMiddleware = require('../middlewares/general.middleware'),
authMiddleware = require('../middlewares/jwt.middleware'),
userMiddleware = require('../middlewares/user.middleware')
/**
* Init all APIs on your application
* @param {*} app from express
*/
let initAPIs = app => {
////////////////////////////////////////////////////////////////////////////
router.use([ generalMiddleware.formatJsonApi, generalMiddleware.setAllowOrigin ])
////////////////////////////////////////////////////////////////////////////
/////////////////// Route không cần login ////////////////////////////////
////////////////////////////////////////////////////////////////////////////
router.post('/register', [ userMiddleware.REGISTER ], userApiController.register)
router.post('/login', [ userMiddleware.LOGIN ], userApiController.login)
////////////////////////////////////////////////////////////////////////////
router.use([ authMiddleware.isAuth])
////////////////////////////////////////////////////////////////////////////
/////////// route cần verify thành công jwt ////////////////////////////////
////////////////////////////////////////////////////////////////////////////
router.get('/users', userApiController.getUser )
return app.use( "/api", router )
}
module.exports = initAPIs
Trong file app.js chỉ cần thực hiện thêm thao tác :
const initAPIs = require("./routes/api")
/// set root api
initAPIs(app)
Xong ! bây giừo chạy npm start lên và xem kết quả: