Hướng dẫn secure nodejs api - api nodejs an toàn
Ở các bài viết trước, VNTALKING đã hướng dẫn các bạn cách xây dựng REST API bằng Node.js + Express. Các bạn có thể xem trước khi quay lại bài viết này: Show 🙋 Tạo RESTful API đơn giản bằng Nodejs + MongoDB Tuy nhiên, còn một công đoạn quan trọng mà bạn cần phải làm trước khi đưa sản phẩm thành product. Đó chính là bảo mật các REST API, hay nói cách khác là tạo secure REST API. Mục tiêu của việc tạo secure REST API là để bạn có thể cấp phép cho một ứng dụng cụ thể nào đó, được phép sử dụng API. Bài viết này, mình sẽ hướng dẫn các bạn tạo secure REST API bằng Node.js. Cụ thể, chúng ta sẽ cùng nhau thực hành tạo API để ứng dụng truy cập vào tài nguyên quản lý người dùng trong hệ thống. API nói chung, REST API nói riêng, là phương thức để cung cấp một dịch vụ mà phía server có thể thực hiện. Vấn đề phát sinh ra ở đây là: Bên dịch vụ cung cấp API muốn xác định được thông tin của phía sử dụng API. Mục đích có thể là:
Đó là lúc chúng ta cần tạo secure Rest API. Thực ra secure REST API vẫn là REST API đó thôi, chỉ là chúng ta cần thêm một luồng để xác thực danh tính người sử dụng API đó, ví dụ thông qua quá trình login chẳng hạn. Giới thiệu dự án tạo secure REST API trong bài viếtMột người dùng (user) có những thông tin sau:
Danh sách các APIs sẽ xây dựng:
Ngoài ra, chúng ta sử dụng JWT (JSON Web Token) cho access token. Để làm được điều này, chúng ta sẽ tạo thêm một resource nữa, gọi là auth, sử dụng email và password để tạo chuỗi token dùng để xác thực giữa client và server. Chúng ta bắt đầu thực hành nhé! Cài đặt môi trườngĐể thực hiện dự án này, chúng ta cần phải cài đặt môi trường phát triển gồm có:
Mình không hướng dẫn cụ thể cách cài đặt trong bài viết này, bạn có thể tham khảo tại đây: Cài đặt NodeJs trên Window, Ubuntu chi tiết Sau khi cài đặt xong, môi trường phát triển đã chuẩn bị xong. Bước tiếp là tạo mới một dự án Node.JS: const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });7 Sau khi tạo xong dự án, để thống nhất danh sách các dependencies sử dụng trong dự án, bạn có thể copy nội dùng package.json và paste vào dự án của bạn { "name": "rest-api-tutorial", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "VNTALKING", "license": "ISC", "dependencies": { "body-parser": "1.7.0", "express": "^4.8.7", "jsonwebtoken": "^7.3.0", "moment": "^2.17.1", "moment-timezone": "^0.5.13", "mongoose": "^5.1.1", "node-uuid": "^1.4.8", "swagger-ui-express": "^2.0.13", "sync-request": "^4.0.2" } } Cuối cùng, chúng ta tạo thêm 3 thư mục, tương ứng 3 modules:
Tạo User ModuleChúng ta sử dụng thư viện Mongoose trong dự án để tương tác với MongoDB. Đầu tiên, tạo User schema: const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });8 const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number }); Sau khi định nghĩa xong schema, bạn dễ dàng attach schema đó vào user model: const userModel = mongoose.model('Users', userSchema); Sau khi định nghĩa model xong, bạn sẽ sử dụng model cho tất cả các thao tác CRUD với DB. API thêm UserỞ phần này, chúng ta sẽ tạo API: const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });9 Chúng ta định nghĩa route cho api này tại const userModel = mongoose.model('Users', userSchema);0 app.post('/users', [ UsersController.insert ]); Phần comtroller, chúng ta sẽ xử lý mã hóa giá trị password, mở const userModel = mongoose.model('Users', userSchema);1 exports.insert = (req, res) => { let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512',salt) .update(req.body.password) .digest("base64"); req.body.password = salt + "$" + hash; req.body.permissionLevel = 1; UserModel.createUser(req.body) .then((result) => { res.status(201).send({id: result._id}); }); }; Ở đoạn code trên, chúng có gọi hàm const userModel = mongoose.model('Users', userSchema);2 để lưu dữ liệu vào DB. Nhưng mà chúng ta chưa có viết hàm này, bạn mà chạy là bị lỗi compile luôn.Mở const userModel = mongoose.model('Users', userSchema);3 và thêm đoạn này: exports.createUser = (userData) => { const user = new User(userData); return user.save(); }; API lấy thông tin một UserỞ phần này, chúng ta sẽ tạo API: const userModel = mongoose.model('Users', userSchema);4 Bạn mở const userModel = mongoose.model('Users', userSchema);5 để định nghĩa router: app.get('/users/:userId', [ UsersController.getById ]); Sau đó, trong controller const userModel = mongoose.model('Users', userSchema);1 exports.getById = (req, res) => { UserModel.findById(req.params.userId).then((result) => { res.status(200).send(result); }); }; Cuối cùng thì sử dụng hàm const userModel = mongoose.model('Users', userSchema);7 trong model ( const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });8)để tìm User trong DB. exports.findById = (id) => { return User.findById(id).then((result) => { result = result.toJSON(); delete result._id; delete result.__v; return result; }); }; Vậy là xong cho API rồi đấy. Các API cho update, xóa và lấy danh sách users, các bạn làm tương tự. Có thể tham khảo trong mã nguồn đầy đủ, mình để ở dưới bài viết. Chúng ta chuyển sang phần trọng tâm của bài viết, đó là xây dựng lớp bảo mật cho API. Tạo Auth moduleTrước khi chúng ta có thể bảo mật user module bằng cách thực hiện phân quyền và xác thực. Chúng ta cần phải tạo mã access token cho người/ứng dụng sử dụng API. Như mình đã đề cập ở trên, chúng ta sẽ sử dụng JWT để tạo access token. Trong bài viết này, nó là một chuỗi JSON được tạo dựa trên thông email và password của người dùng. Tất nhiên, chúng ta sẽ bổ sung thêm thời hạn mà mã access token này hết hạn để gia tăng tính bảo mật. Đầu tiên, chúng ta định nghĩa route ( const userModel = mongoose.model('Users', userSchema);9), dữ liệu trong body gồm thông tin email và password, ví dụ như dưới đây: { "email" : "[email protected]", "password" : "s3cr3tp4sswo4rd2" } Trước khi chuyển sang controller, chúng ta tạo một middleware để xác thực ( app.post('/users', [ UsersController.insert ]);0) const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });0 Ok, giờ thì chuyển sang controller và tạo mã JWT const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });1 Mặc dù trong bài viết này, mình không có implement việc tạo mới access token khi mã token cũ hết hạn. Nhưng mình cũng để sẵn code để bạn thêm vào trong tương lai một cách dễ dàng. Cuối cùng là tạo route và gọi middleware tương ứng const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });2 Kết quả thu được mã access toke giống như thế này const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });3 Sau khi ứng dụng client có mã token này, nó có thể gửi mã token đó lên server mỗi khi truy cập vào API, thay vì phải gửi email hay mật khẩu. Tạo Permissions và Validations MiddlewareĐầu tiên, chúng ta cần xác định “ai” được phép sử dụng user resource thông qua API này. Đây là một user case mà chúng ta cần xử lý:
Sau khi xác định được các user case, bước tiếp theo là tạo middleware để luôn validate thông tin user nếu user này có một mã JWT hợp lệ. Mở app.post('/users', [ UsersController.insert ]);1 const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });4 Trong này, có các mã HTTP code để xử lý lỗi:
Ví dụ một middleware cho phân quyền: const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });5 Công việc cuối cùng là thêm authentication middleware vào route const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });6 Vậy là xong rồi đấy Tạm kếtTrên đây là hướng dẫn một phương pháp để bạn bảo mật cho REST API của mình. Mình hi vọng, qua bài viết này, các bạn hiểu hơn về Node.js và cách REST API trên Node.js Toàn bộ mã nguồn minh họa trong bài viết này, các bạn tải ở đây nhé: Đừng quên để lại vài lời bình luận làm động lực cho tác giả nhé 🔥 Đọc thêm về Node.js
Nguồn tham khảo: Toptal – Creating a Secure REST API in Node.js, Node.JS Official document |