Xây dựng xác thực vào một ứng dụng là một nhiệm vụ tẻ nhạt. Tuy nhiên, đảm bảo xác thực này là không thể phá vỡ thậm chí còn khó hơn. Là nhà phát triển, chúng ta không thể kiểm soát được người dùng làm gì với mật khẩu của họ, cách họ bảo vệ mật khẩu, họ cung cấp mật khẩu cho ai hoặc cách họ tạo mật khẩu. Tất cả những gì chúng ta có thể làm là đến đủ gần để đảm bảo rằng yêu cầu xác thực được thực hiện bởi người dùng của chúng ta chứ không phải người khác. OTP chắc chắn giúp ích cho việc đó và các dịch vụ như Twilio Verify giúp chúng ta tạo OTP an toàn nhanh chóng mà không cần phải bận tâm đến logic.
OTP bổ sung thêm một lớp bảo mật cho các ứng dụng mà hệ thống xác thực mật khẩu truyền thống không thể cung cấp. OTP được tạo ngẫu nhiên và chỉ có hiệu lực trong một thời gian ngắn, tránh được một số thiếu sót liên quan đến xác thực dựa trên mật khẩu truyền thống.
OTP có thể được sử dụng để thay thế mật khẩu truyền thống hoặc củng cố mật khẩu bằng cách sử dụng phương pháp xác thực hai yếu tố (2FA). Về cơ bản, OTP có thể được sử dụng ở bất cứ đâu bạn cần để đảm bảo danh tính của người dùng bằng cách dựa vào phương tiện truyền thông cá nhân do người dùng sở hữu, chẳng hạn như điện thoại, email, v.v.
Bài viết này dành cho các nhà phát triển muốn tìm hiểu về:
Lưu ý: Tôi muốn đề cập rằng chúng tôi sẽ sử dụng một số gói của bên thứ 3 (do người khác xây dựng) trong ứng dụng của mình. Đây là một thông lệ phổ biến, vì không cần phải phát minh lại bánh xe. Chúng tôi có thể tạo máy chủ nút của riêng mình không? Có, tất nhiên rồi. Tuy nhiên, thời gian đó có thể được sử dụng tốt hơn vào việc xây dựng logic dành riêng cho ứng dụng của chúng tôi.
Trong thư mục
Để duy trì kiến trúc Model View Controller (MVC), chúng ta phải tạo các thư mục sau trong thư mục
Nhiều nhà phát triển có những lý do khác nhau để sử dụng kiến trúc MVC, nhưng đối với cá nhân tôi, đó là vì:
Thêm tập lệnh bên dưới vào tệp
Tạo tệp
Chúng ta phải
Chúng ta hãy phân tích đoạn mã trên.
Ngoài các câu lệnh
Các hàm trung gian là các hàm có quyền truy cập vào đối tượng yêu cầu, đối tượng phản hồi và hàm trung gian tiếp theo trong chu kỳ yêu cầu và phản hồi của ứng dụng.
Cuối cùng, chúng ta yêu cầu máy chủ ứng dụng của mình lắng nghe các yêu cầu đến cổng 3000.
Sau đó, trong thiết bị đầu cuối, hãy chạy:
Nếu bạn thấy
Để cài đặt MongoDB trên máy của bạn, hãy truy cập tài liệu của MongoDB.
Để tích hợp MongoDB vào ứng dụng express của mình, chúng ta sẽ sử dụng Mongoose. Mongoose là một ODM (viết tắt của
Về cơ bản, Mongoose giúp chúng ta sử dụng MongoDB trong ứng dụng dễ dàng hơn bằng cách tạo một trình bao bọc xung quanh các hàm MongoDB gốc.
Trong
Hàm
Định dạng cho chuỗi kết nối là
Mongoose kết nối tới cơ sở dữ liệu có tên là
Chúng ta tạo mô hình người dùng trong thư mục
Một
Mặc dù bạn có thể tạo dữ liệu không có lược đồ trong MongoDB, nhưng không nên làm như vậy — tin tôi đi, dữ liệu của bạn sẽ rất lộn xộn. Bên cạnh đó, lược đồ rất tuyệt. Chúng cho phép bạn chỉ định cấu trúc và hình thức của các đối tượng trong cơ sở dữ liệu của mình — ai lại không muốn có những quyền hạn như vậy chứ?
Trong
Đoạn mã trên thực hiện một số việc. Chúng ta hãy cùng xem chúng.
Tiếp theo,
Chúng ta sẽ có một thư mục
Về cơ bản,
Trong
Sau đó:
Trong
Tệp
Chúng ta sẽ sử dụng Semantic UI làm khung CSS của chúng ta.
Chúng ta sẽ tạo hai tệp trong thư mục
Trong
Trong
Tại đây, chúng tôi bao gồm
Tại đây, chúng tôi cung cấp một biểu mẫu để người dùng nhập mã xác minh sẽ được gửi cho họ.
Trong
Cuối cùng, chúng ta tạo một tệp

Trong
Chúng tôi đang thêm một số
Đừng lo lắng nhiều về hàm
Tiếp theo, chúng ta tạo thư mục
Trong
Hãy cùng tìm hiểu những gì đang diễn ra trong đoạn mã trên.
Ngoài các câu lệnh require, chúng ta tạo hàm
Chúng ta chỉ định rằng
Trong
Tệp phần mềm trung gian của chúng tôi chứa hai (2)
Trong
Trong
Hiện tại,
Tiếp theo, chúng ta cần cài đặt
Chúng tôi tạo một tệp trong thư mục gốc của dự án và đặt tên là
Điều này yêu cầu git bỏ qua cả thư mục
Để lấy thông tin xác thực tài khoản Twilio, chúng tôi đăng nhập vào bảng điều khiển Twilio và sao chép
Trong
Chúng ta tạo một tệp có tên
Trong
Trong đoạn mã trên, chúng ta tạo một dịch vụ
Chạy:
Chuỗi được ghi vào màn hình của chúng ta là
Trong
Trong
Trong
Chúng tôi đã tạo thêm hai
Trong
Trong
Chúng ta đang tạo các tuyến đường của mình trong đoạn mã trên, hãy cùng xem những gì đang diễn ra ở đây:
Cú pháp
Nó thực hiện cùng một chức năng như
Ứng dụng express đầy đủ của chúng ta sẽ được thiết lập và chạy.
Những gì bạn có thể làm tiếp theo:
Mật khẩu có vấn đề gì?
Các nhà phát triển phải đối mặt với một số vấn đề khi chỉ sử dụng xác thực dựa trên mật khẩu vì nó có các vấn đề sau:- Người dùng có thể quên mật khẩu và ghi lại (khiến mật khẩu có thể bị đánh cắp);
- Người dùng có thể sử dụng lại mật khẩu trên nhiều dịch vụ (khiến tất cả tài khoản của họ dễ bị xâm phạm dữ liệu một lần);
- Người dùng có thể sử dụng mật khẩu dễ nhớ, khiến chúng tương đối dễ bị hack.
Nhập OTP
Mật khẩu dùng một lần (OTP) là mật khẩu hoặc mã PIN chỉ có hiệu lực cho một phiên đăng nhập hoặc giao dịch. Một khi nó chỉ có thể được sử dụng một lần, tôi chắc chắn bạn có thể thấy cách sử dụng OTP bù đắp cho những thiếu sót của mật khẩu truyền thống.OTP bổ sung thêm một lớp bảo mật cho các ứng dụng mà hệ thống xác thực mật khẩu truyền thống không thể cung cấp. OTP được tạo ngẫu nhiên và chỉ có hiệu lực trong một thời gian ngắn, tránh được một số thiếu sót liên quan đến xác thực dựa trên mật khẩu truyền thống.
OTP có thể được sử dụng để thay thế mật khẩu truyền thống hoặc củng cố mật khẩu bằng cách sử dụng phương pháp xác thực hai yếu tố (2FA). Về cơ bản, OTP có thể được sử dụng ở bất cứ đâu bạn cần để đảm bảo danh tính của người dùng bằng cách dựa vào phương tiện truyền thông cá nhân do người dùng sở hữu, chẳng hạn như điện thoại, email, v.v.
Bài viết này dành cho các nhà phát triển muốn tìm hiểu về:
- Tìm hiểu cách xây dựng ứng dụng express.js đầy đủ;
- Triển khai xác thực bằng passport.js;
- Cách Twilio Verify để xác minh người dùng qua điện thoại.
Lưu ý: Tôi muốn đề cập rằng chúng tôi sẽ sử dụng một số gói của bên thứ 3 (do người khác xây dựng) trong ứng dụng của mình. Đây là một thông lệ phổ biến, vì không cần phải phát minh lại bánh xe. Chúng tôi có thể tạo máy chủ nút của riêng mình không? Có, tất nhiên rồi. Tuy nhiên, thời gian đó có thể được sử dụng tốt hơn vào việc xây dựng logic dành riêng cho ứng dụng của chúng tôi.
Mục lục
- Tổng quan cơ bản về Xác thực trong các ứng dụng web;
- Xây dựng máy chủ Express;
- Tích hợp MongoDB vào ứng dụng Express của chúng tôi;
- Xây dựng chế độ xem của ứng dụng của chúng tôi bằng công cụ tạo mẫu EJS;
- Xác thực cơ bản bằng số hộ chiếu;
- Sử dụng Twilio Verify để bảo vệ các tuyến đường.
Yêu cầu
- Node.js
- MongoDB
- Trình soạn thảo văn bản (ví dụ: VS Code)
- Trình duyệt web (ví dụ: Chrome, Firefox)
- Hiểu biết về HTML, CSS, JavaScript, Express.js
Tổng quan cơ bản về xác thực trong ứng dụng web
Xác thực là gì?
Xác thực là toàn bộ quá trình xác định người dùng và xác minh rằng người dùng có tài khoản trên ứng dụng của chúng tôi.Nói như vậy, chúng ta hãy xem quyền hạn là gì.Xác thực không nên nhầm lẫn với ủy quyền. Mặc dù chúng hoạt động song song với nhau, nhưng không có quyền hạn nào mà không có xác thực.
Quyền hạn là gì?
Quyền hạn về cơ bản nhất là về quyền của người dùng — những gì người dùng được phép làm trong ứng dụng. Nói cách khác:- Xác thực: Bạn là ai?
- Quyền hạn: Bạn có thể làm gì?
Cách xác thực người dùng phổ biến nhất là thông quaXác thực diễn ra trước Quyền hạn.
Không có Quyền hạn nào mà không có Xác thực.
tên người dùng
và mật khẩu
.Thiết lập ứng dụng của chúng tôi
Để thiết lập ứng dụng, chúng tôi tạo thư mục dự án:
Mã:
mkdir authWithTwilioVerify
Xây dựng máy chủ Express
Chúng tôi sẽ sử dụng Express.js để xây dựng máy chủ của mình.Tại sao chúng ta cần Express?
Xây dựng máy chủ trongNode
có thể rất tẻ nhạt, nhưng các khuôn khổ giúp chúng ta thực hiện mọi việc dễ dàng hơn.Express
là khuôn khổ web Node
phổ biến nhất. Nó cho phép chúng ta:- Viết trình xử lý cho các yêu cầu có các động từ
HTTP
khác nhau tại các đường dẫnURL
khác nhau (tuyến đường); - Tích hợp với các công cụ kết xuất
view
để tạo phản hồi bằng cách chèn dữ liệu vào các mẫu; - Đặt các thiết lập ứng dụng web chung — như
cổng
được sử dụng để kết nối và vị trí của các mẫu được sử dụng để kết xuất phản hồi; - Thêm
phần mềm trung gian
xử lý yêu cầu bổ sung tại bất kỳ điểm nào trong đường ống xử lý yêu cầu.
Trong thư mục
authWithTwilioVerify
của chúng tôi, chúng tôi khởi tạo package.json
chứa thông tin liên quan đến dự án của chúng tôi.
Mã:
cd authWithTwilioVerifynpm init -y
authWithTwilioVerify
của mình:
Mã:
mkdir public controllers views route config models
- Nó khuyến khích việc tách biệt các mối quan tâm;
- Nó giúp viết mã sạch;
- Nó cung cấp một cấu trúc cho cơ sở dữ liệu của tôi và vì các nhà phát triển khác cũng sử dụng nó nên việc hiểu cơ sở dữ liệu sẽ không thành vấn đề.
-
Controllers
chứa các bộ điều khiển; -
Models
chứa các mô hình cơ sở dữ liệu của chúng ta; -
Public
chứa các tài sản tĩnh của chúng ta, ví dụ: Tệp CSS, hình ảnh, v.v.; -
Thư mục Views
chứa các trang sẽ được hiển thị trong trình duyệt; -
Thư mục Routes
chứa các tuyến đường khác nhau của ứng dụng; -
Thư mục Config
chứa thông tin đặc thù của ứng dụng.
-
nodemon
tự động khởi động lại máy chủ khi chúng ta thực hiện thay đổi; -
express
cung cấp cho chúng ta giao diện đẹp để xử lý các tuyến đường; -
express-session
cho phép chúng ta xử lý các phiên dễ dàng trong ứng dụng express của mình; -
connect-flash
cho phép chúng ta hiển thị thông báo cho người dùng.
Mã:
npm install nodemon -D
package.json
để khởi động máy chủ của chúng ta bằng nodemon
.
Mã:
"scripts": { "dev": "nodemon index" },
Mã:
npm install express express-session connect-flash --save
index.js
và thêm các gói cần thiết cho ứng dụng của chúng ta.Chúng ta phải
require
các gói đã cài đặt vào tệp index.js
của mình để ứng dụng chạy tốt, sau đó chúng ta định cấu hình các gói như sau:
Mã:
const path = require('path')const express = require('express');const session = require('express-session')const flash = require('connect-flash')const port = process.env.PORT || 3000const app = express();app.use('/static', express.static(path.join(__dirname, 'public')))app.use(session({ secret: "please log me in", resave: true, saveUninitialized: true }));app.use(express.json())app.use(express.urlencoded({ extended: true }))// Kết nối flashapp.use(flash());// Biến toàn cụcapp.use(function(req, res, next) { res.locals.success_msg = req.flash('success_msg'); res.locals.error_msg = req.flash('error_msg'); res.locals.error = req.flash('error'); res.locals.error = req.flash('error'); res.locals.user = req.user next();});//define error handlerapp.use(function(err, req, res, next) { res.render('error', { error : err })})//listen on portapp.listen(port, () => { console.log(`app is running on port ${port}`)});
Ngoài các câu lệnh
require
, chúng ta sử dụng hàm app.use()
— cho phép chúng ta sử dụng middleware
cấp ứng dụng.Các hàm trung gian là các hàm có quyền truy cập vào đối tượng yêu cầu, đối tượng phản hồi và hàm trung gian tiếp theo trong chu kỳ yêu cầu và phản hồi của ứng dụng.
Giống như việc chuyển trạng thái ứng dụng cho hàm phần mềm trung gian, nói rằng đây là trạng thái, bạn muốn làm gì với nó và gọi hàmHầu hết các gói có quyền truy cập vào trạng thái của ứng dụng (đối tượng yêu cầu và phản hồi) và có thể thay đổi các trạng thái đó thường được sử dụng làm phần mềm trung gian. Về cơ bản, phần mềm trung gian bổ sung chức năng cho ứng dụng express của chúng ta.
next()
cho phần mềm trung gian tiếp theo.Cuối cùng, chúng ta yêu cầu máy chủ ứng dụng của mình lắng nghe các yêu cầu đến cổng 3000.
Sau đó, trong thiết bị đầu cuối, hãy chạy:
Mã:
npm run dev
ứng dụng đang chạy trên cổng 3000
trong thiết bị đầu cuối, điều đó có nghĩa là ứng dụng của chúng ta đang chạy bình thường.Tích hợp MongoDB vào ứng dụng Express của chúng ta
MongoDB lưu trữ dữ liệu dưới dạng tài liệu. Các tài liệu này được lưu trữ trong MongoDB theo định dạng JSON (Ký hiệu đối tượng JavaScript). Vì chúng ta đang sử dụng Node.js, nên việc chuyển đổi dữ liệu được lưu trữ trong MongoDB thành các đối tượng JavaScript và thao tác chúng khá dễ dàng.Để cài đặt MongoDB trên máy của bạn, hãy truy cập tài liệu của MongoDB.
Để tích hợp MongoDB vào ứng dụng express của mình, chúng ta sẽ sử dụng Mongoose. Mongoose là một ODM (viết tắt của
object data mapper
).Về cơ bản, Mongoose giúp chúng ta sử dụng MongoDB trong ứng dụng dễ dàng hơn bằng cách tạo một trình bao bọc xung quanh các hàm MongoDB gốc.
Mã:
npm install mongoose --save
index.js
, nó yêu cầu mongoose
:
Mã:
const mongoose = require('mongoose')const app = express()//kết nối với mongodbmongoose.connect('mongodb://localhost:27017/authWithTwilio',{ useNewUrlParser: true, useUnifiedTopology: true}).then(() => { console.log(`đã kết nối với mongodb`)}).catch(e => console.log(e))
mongoose.connect()
cho phép chúng ta thiết lập kết nối tới cơ sở dữ liệu MongoDB của mình bằng chuỗi kết nối.Định dạng cho chuỗi kết nối là
mongodb://localhost:27017/{database_name}
.mongodb://localhost:27017/
là máy chủ mặc định của MongoDB và database_name
là bất kỳ tên nào chúng ta muốn gọi cơ sở dữ liệu của mình.Mongoose kết nối tới cơ sở dữ liệu có tên là
database_name
. Nếu không tồn tại, nó sẽ tạo một cơ sở dữ liệu với database_name
và kết nối với cơ sở dữ liệu đó.Mongoose.connect()
là một lời hứa, vì vậy, việc ghi nhật ký thông báo vào bảng điều khiển trong các phương thức then()
và catch()
để cho chúng ta biết kết nối có thành công hay không luôn là một cách làm tốt.Chúng ta tạo mô hình người dùng trong thư mục
models
của mình:
Mã:
cd modelstouch user.js
user.js
yêu cầu mongoose và tạo lược đồ người dùng của chúng ta:
Mã:
const mongoose = require('mongoose');const userSchema = new mongoose.Schema({ name : { type: String, required: true }, username : { type: String, required: true }, password : { type: String, required: true }, phonenumber : { type: String, required: true }, email : { type: String, required: true }, verified: Boolean})module.exports = mongoose.model('user', userSchema)
schema
cung cấp một cấu trúc cho dữ liệu của chúng ta. Nó cho thấy cách dữ liệu nên được cấu trúc trong cơ sở dữ liệu. Theo đoạn mã trên, chúng tôi chỉ định rằng một đối tượng người dùng trong cơ sở dữ liệu phải luôn có name
, username
, password
, phonenumber
và email
. Vì những trường đó là bắt buộc, nếu dữ liệu được đẩy vào cơ sở dữ liệu thiếu bất kỳ trường bắt buộc nào trong số này, mongoose sẽ báo lỗi.Mặc dù bạn có thể tạo dữ liệu không có lược đồ trong MongoDB, nhưng không nên làm như vậy — tin tôi đi, dữ liệu của bạn sẽ rất lộn xộn. Bên cạnh đó, lược đồ rất tuyệt. Chúng cho phép bạn chỉ định cấu trúc và hình thức của các đối tượng trong cơ sở dữ liệu của mình — ai lại không muốn có những quyền hạn như vậy chứ?
Mã hóa mật khẩu
Lý do chúng ta cần mã hóa mật khẩu người dùng là: trong trường hợp ai đó bằng cách nào đó truy cập vào cơ sở dữ liệu của chúng ta, chúng ta có thể đảm bảo rằng mật khẩu người dùng được an toàn — vì tất cả những gì người này thấy sẽ chỉ là mộtCảnh báo: không bao giờ lưu trữ mật khẩu của người dùng dưới dạng văn bản thuần túy trong cơ sở dữ liệu của bạn.
Luôn mã hóa mật khẩu trước khi đẩy chúng vào cơ sở dữ liệu.
băm
. Điều này cung cấp một số mức độ đảm bảo an ninh, nhưng một hacker tinh vi vẫn có thể bẻ khóa băm
này nếu họ có các công cụ phù hợp. Do đó cần có OTP, nhưng chúng ta hãy tập trung vào việc mã hóa mật khẩu người dùng ngay bây giờ.bcryptjs
cung cấp một cách để mã hóa và giải mã mật khẩu của người dùng.
Mã:
npm install bcryptjs
models/user.js
, nó yêu cầu bcryptjs
:
Mã:
//sau khi yêu cầu mongooseconst bcrypt = require('bcryptjs')//trước module.exports//băm mật khẩu khi lưuuserSchema.pre('save', async function() { return new Promise( async (resolve, reject) => { await bcrypt.genSalt(10, async (err, salt) => { await bcrypt.hash(this.password, salt, async (err, hash) => { if(err) { reject (err) } else { resolve (this.password = hash) } }); }); })})userSchema.methods.validPassword = async function(password) { return new Promise((resolve, reject) => { bcrypt.compare(password, this.password, (err, res) => { if(err) { reject (err) } resolve (res) }); })}
userSchema.pre('save', callback)
là móc mongoose
cho phép chúng ta thao tác dữ liệu trước khi lưu vào cơ sở dữ liệu. Trong hàm gọi lại
, chúng ta trả về một lời hứa cố gắng băm(encrypt) bcrypt.hash()
mật khẩu bằng cách sử dụng bcrypt.genSalt()
mà chúng ta đã tạo. Nếu xảy ra lỗi trong quá trình băm
này, chúng ta từ chối
hoặc giải quyết
bằng cách đặt this.password = hash
. this.password
là userSchema password
.Tiếp theo,
mongoose
cung cấp cho chúng ta một cách để thêm các phương thức vào lược đồ bằng cách sử dụng schema.methods.method_name
. Trong trường hợp của chúng ta, chúng ta đang tạo một phương thức cho phép chúng ta xác thực mật khẩu người dùng. Gán giá trị hàm cho *userSchema.methods.validPassword*
, chúng ta có thể dễ dàng sử dụng phương thức so sánh bcryptjs bcryprt.compare()
để kiểm tra xem mật khẩu có đúng hay không.bcrypt.compare()
lấy hai đối số và một lệnh gọi lại. password
là mật khẩu được truyền khi gọi hàm, trong khi this.password
là mật khẩu từ userSchema.Hy vọng bạn có thể thấy được tính hữu ích của mongoose. Bên cạnh việc tạo ra một lược đồ cung cấp cấu trúc cho các đối tượng cơ sở dữ liệu của chúng ta, nó cũng cung cấp các phương thức hay để thao tác các đối tượng đó — nếu không thì sẽ khá rắc rối khi chỉ sử dụng MongoDB gốc.Tôi thích phương pháp xác thực mật khẩu của người dùng này vì nó giống như một thuộc tính trên đối tượng người dùng. Người ta có thể dễ dàng gọiUser.validPassword(password)
và nhận được true hoặc false làm phản hồi.
Express đối với Node, cũng như Mongoose đối với MongoDB.
Xây dựng các chế độ xem của ứng dụng bằng EJS Templating Engine
Trước khi bắt đầu xây dựng các chế độ xem của ứng dụng, chúng ta hãy xem qua kiến trúc giao diện người dùng của ứng dụng.Kiến trúc giao diện người dùng
EJS
là một công cụ tạo mẫu hoạt động trực tiếp với Express. Không cần một khuôn khổ giao diện người dùng khác. EJS
giúp việc truyền dữ liệu trở nên rất dễ dàng. Nó cũng giúp theo dõi những gì đang diễn ra dễ dàng hơn vì không cần chuyển đổi từ back-end sang front-end.Chúng ta sẽ có một thư mục
views
, chứa các tệp sẽ được hiển thị trong trình duyệt. Tất cả những gì chúng ta phải làm là gọi phương thức res.render()
từ bộ điều khiển của mình. Ví dụ, nếu chúng ta muốn hiển thị trang đăng nhập, chỉ cần gọi res.render('login')
. Chúng ta cũng có thể truyền dữ liệu cho các chế độ xem bằng cách thêm một đối số bổ sung — là một đối tượng cho phương thức render()
, như res.render('dashboard', { user })
. Sau đó, trong view
của mình, chúng ta có thể hiển thị dữ liệu bằng cú pháp đánh giá
. Mọi thứ có thẻ này đều được đánh giá — ví dụ,
hiển thị giá trị của thuộc tính username của đối tượng người dùng. Ngoài cú pháp đánh giá, EJS
còn cung cấp cú pháp điều khiển (
), cho phép chúng ta viết các câu lệnh điều khiển chương trình như điều kiện, vòng lặp, v.v.Về cơ bản,
EJS
cho phép chúng ta nhúng JavaScript vào HTML của mình.
Mã:
npm install ejs express-ejs-layouts --save
index.js
, nó yêu cầu express-ejs-layouts
:
Mã:
//sau khi yêu cầu connect-flashconst expressLayouts = require('express-ejs-layouts')//sau logic mongoose.connectapp.use(expressLayouts);app.set('view engine', 'ejs');
Mã:
cd viewstouch layout.ejs
views/layout.ejs
,
Mã:
[*] Xác thực Node js
layout.ejs
đóng vai trò như một tệp index.html
, nơi chúng ta có thể đưa vào tất cả các tập lệnh và bảng định kiểu của mình. Sau đó, trong div
với các lớp ui container
, chúng ta sẽ render body
— là phần còn lại của các chế độ xem ứng dụng của chúng ta.Chúng ta sẽ sử dụng Semantic UI làm khung CSS của chúng ta.
Xây dựng các thành phần
Các thành phần là nơi chúng ta lưu trữ mã có thể sử dụng lại, do đó chúng ta không phải viết lại chúng mỗi lần. Tất cả những gì chúng ta làm là bao gồm chúng ở bất cứ nơi nào cần thiết.Ví dụ, chúng ta muốn partials cho menu của mình, để chúng ta không phải viết mã cho nó mỗi lần chúng ta cần menu trên trang của mình.Bạn có thể nghĩ về các thành phần giống như các thành phần trong các khung giao diện người dùng: chúng khuyến khích mã DRY và khả năng tái sử dụng mã. Hãy nghĩ về partials như một phiên bản trước đó của các thành phần.
Mã:
cd viewsmkdir partials
/views/partials
:
Mã:
cd partialstouch menu.ejs message.ejs
menu.ejs
,
Mã:
Trang chủ bảng điều khiển Đăng xuất Đăng ký Đăng nhập
message.ejs
,
Mã:
[I][/I] Đăng ký người dùng không thành công [I][/I] Đăng ký người dùng của bạn đã thành công. [I][/I] [I][/I]
Xây dựng Trang Bảng điều khiển
Trong thư mục chế độ xem của chúng tôi, chúng tôi tạo tệpdashboard.ejs
:
Mã:
[HEADING=1] DashBoard[/HEADING]
menu partials
để chúng tôi có menu trên trang.Xây dựng trang lỗi
Trong thư mục chế độ xem của chúng tôi, chúng tôi tạo tệperror.ejs
:
Mã:
[HEADING=1]Trang lỗi[/HEADING]
Xây dựng Trang chủ
Trong thư mục chế độ xem của chúng tôi, chúng tôi tạo tệphome.ejs
:
Mã:
[HEADING=1] Chào mừng đến với Trang chủ[/HEADING]
Xây dựng Trang đăng nhập
Trong thư mục chế độ xem, chúng tôi tạo tệplogin.ejs
:
Mã:
[HEADING=3] Biểu mẫu đăng nhập [/HEADING] Email Password Login
Xây dựng trang xác minh
Trong thư mục chế độ xem của chúng tôi, chúng tôi tạo tệplogin.ejs
:
Mã:
[HEADING=1]Xác minh trang[/HEADING]
vui lòng xác minh tài khoản của bạn
verification code Verify
Gửi lại mã
Xây dựng trang đăng ký
Chúng tôi cần lấy số điện thoại di động của người dùng và chúng ta đều biết rằng mã quốc gia khác nhau tùy theo quốc gia. Do đó, chúng ta sẽ sử dụng[intl-tel-input](https://intl-tel-input.com/)
để giúp chúng ta với mã quốc gia và xác thực số điện thoại.
Mã:
npm install intl-tel-input
Trong thư mục public của chúng ta, chúng ta tạo một thư mụccss
, thư mụcjs
và thư mụcimg
:
Mã:cd publicmkdir css js img
-
Chúng ta sao chép tệpintlTelInput.css
từ tệpnode_modules\intl-tel-input\build\css\
vào thư mụcpublic/css
của chúng ta.
-
Chúng ta sao chép cảintlTelInput.js
vàutils.js
từ thư mụcnode_modules\intl-tel-input\build\js\
vào thư mụcpublic/js
của chúng tôi.
-
Chúng tôi sao chép cảflags.png
và[email protected]
từ thư mụcnode_modules\intl-tel-input\build\img\
vào thư mụcpublic/img
của chúng tôi.
public/css
của chúng tôi:
Mã:
cd publictouch app.css
app.css
, thêm các kiểu bên dưới:
Mã:
.iti__flag {background-image: url("/static/img/flags.png");}@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .iti__flag {background-image: url("/static/img/[email protected]");}}.hide { display: none}.error { color: red; outline: 1px solid red;}.success{ color: green;}
signup.ejs
trong thư mục chế độ xem của mình:
Mã:
[HEADING=3] Biểu mẫu đăng ký [/HEADING] Tên Tên người dùng Mật khẩu Số điện thoại ✓ Hợp lệ Email Đăng ký const input = document.querySelector("#phone") const errorMsg = document.querySelector("#error-msg") const validMsg = document.querySelector("#valid-msg") const errorMap = ["Số không hợp lệ", "Mã quốc gia không hợp lệ", "Quá ngắn", "Quá dài", "Số không hợp lệ"]; const iti = window.intlTelInput(input, { separateDialCode: true, autoPlaceholder: "aggressive", hiddenInput: "phonenumber", utilsScript: "/static/js/utils.js?1590403638580" // chỉ để định dạng/giữ chỗ, v.v. }); var reset = function() { input.classList.remove("error"); errorMsg.innerHTML = ""; errorMsg.classList.add("hide"); validMsg.classList.add("hide"); }; // khi làm mờ: xác thực input.addEventListener('blur', function() { reset(); if (input.value.trim()) { if (iti.isValidNumber()) { validMsg.classList.remove("hide"); } else { input.classList.add("error"); var errorCode = iti.getValidationError(); errorMsg.innerHTML = errorMap[errorCode]; errorMsg.classList.remove("hide"); } } }); // khi keyup / thay đổi cờ: đặt lại input.addEventListener('change', reset); input.addEventListener('keyup', reset); document.querySelector('.ui.form').addEventListener('submit', (e) => { if(!iti.isValidNumber()){ e.preventDefault() } })

Xác thực cơ bản với Passport
Việc xây dựng xác thực vào ứng dụng có thể thực sự phức tạp và tốn thời gian, vì vậy chúng ta cần một gói để giúp chúng ta thực hiện việc đó.Hãy nhớ: đừng phát minh lại bánh xe, trừ khi ứng dụng của bạn có nhu cầu cụ thể.
passport
là một gói giúp với xác thực trong ứng dụng express của chúng ta.passport
có nhiều chiến lược mà chúng ta có thể sử dụng, nhưng chúng ta sẽ sử dụng local-strategy
— về cơ bản là thực hiện xác thực tên người dùng và mật khẩu
.Một lợi thế của việc sử dụng passport là vì nó có nhiều chiến lược, chúng ta có thể dễ dàng mở rộng ứng dụng của mình để sử dụng các chiến lược khác của nó.
Mã:
npm install passport passport-local
index.js
, chúng ta thêm mã sau:
Mã:
//sau khi yêu cầu expressconst passport = require('passport')//sau khi yêu cầu mongooseconst { localAuth } = require('./config/passportLogic')//sau khi const app = express()localAuth(passport)//sau khi app.use(express.urlencoded({ extended: true }))app.use(passport.initialize());app.use(passport.session());
phần mềm trung gian cấp ứng dụng
vào tệp index.js
của mình — cho biết ứng dụng sử dụng passport.initialize()
và phần mềm trung gian passport.session()
.Passport.initialize()
khởi tạo passport
, trong khi phần mềm trung gian passport.session()
cho passport
biết rằng chúng tôi đang sử dụng session
để xác thực.Đừng lo lắng nhiều về hàm
localAuth()
. Điều đó lấy đối tượng passport
làm đối số và chúng ta sẽ tạo hàm ngay bên dưới.Tiếp theo, chúng ta tạo thư mục
config
và tạo các tệp cần thiết:
Mã:
mkdir configtouch passportLogic.js middleware.js
passportLogic.js
,
Mã:
//tệp chứa logic hộ chiếu để đăng nhập cục bộconst LocalStrategy = require('passport-local').Strategy;const mongoose = require('mongoose')const User = require('../models/user')const localAuth = (passport) => { passport.use( new LocalStrategy( { usernameField: 'email' }, async(email, password, done) => { try { const user = await User.findOne({ email: email }) if (!user) { return done(null, false, { message: 'Email không đúng' }); } //xác thực mật khẩu const valid = await user.validPassword(password) if (!valid) { return done(null, false, { message: 'Mật khẩu không đúng.' }); } return done(null, user); } catch (lỗi) { return done(lỗi) } } )); passport.serializeUser(hàm(người dùng, xong) { xong(null, user.id); }); passport.deserializeUser(hàm(id, xong) { User.findById(id, hàm(err, user) { xong(err, user); }); });}module.exports = { localAuth}
Ngoài các câu lệnh require, chúng ta tạo hàm
localAuth()
, hàm này sẽ được xuất từ tệp. Trong hàm, chúng ta gọi hàm passport.use()
sử dụng LocalStrategy()
để xác thực dựa trên username
và password
.Chúng ta chỉ định rằng
usernameField
của chúng ta phải là email
. Sau đó, chúng ta tìm một người dùng có email
cụ thể đó — nếu không có email nào tồn tại, chúng ta sẽ trả về lỗi trong hàm done()
. Tuy nhiên, nếu người dùng tồn tại, chúng ta sẽ kiểm tra xem mật khẩu có hợp lệ hay không bằng phương thức validPassword
trên đối tượng User
. Nếu mật khẩu không hợp lệ, chúng ta sẽ trả về lỗi. Cuối cùng, nếu mọi thứ thành công, chúng tôi trả về user
trong done(null, user)
.passport.serializeUser()
và passport.deserializeUser()
giúp hỗ trợ các phiên đăng nhập. Passport sẽ tuần tự hóa và hủy tuần tự hóa các phiên bản user
thành và từ phiên.Trong
middleware.js
,
Mã:
//kiểm tra xem người dùng đã được xác minh chưaconst isLoggedIn = async(req, res, next) => { if(req.user){ return next() } else { req.flash( 'error_msg', 'Bạn phải đăng nhập để thực hiện điều đó' ) res.redirect('/users/login') }}const notLoggedIn = async(req, res, next) => { if(!req.user) { return next() } else{ res.redirect('back') }}module.exports = { isLoggedIn, notLoggedIn}
phần mềm trung gian cấp tuyến đường
, sẽ được sử dụng sau trong các tuyến đường của chúng tôi.Phần mềm trung gian cấp tuyến đường được các tuyến đường của chúng tôi sử dụng, chủ yếu để bảo vệ và xác thực tuyến đường, chẳng hạn như ủy quyền, trong khi phần mềm trung gian cấp ứng dụng được toàn bộ ứng dụng sử dụng.
isLoggedIn
và notLoggedIn
là phần mềm trung gian cấp tuyến đường
kiểm tra xem người dùng đã đăng nhập hay chưa. Chúng tôi sử dụng các phần mềm trung gian này để chặn quyền truy cập vào các tuyến đường mà chúng tôi muốn người dùng đã đăng nhập có thể truy cập.Xây dựng Bộ điều khiển đăng ký
Mã:
cd controllersmkdir signUpController.js loginController.js
signUpController.js
, chúng tôi:- Kiểm tra thông tin xác thực của người dùng;
- Kiểm tra xem người dùng có thông tin chi tiết đó (email hoặc số điện thoại) có tồn tại trong cơ sở dữ liệu của chúng tôi không;
- Tạo lỗi nếu người dùng đó tồn tại;
- Cuối cùng, nếu người dùng đó không tồn tại, chúng tôi tạo một người dùng mới với các thông tin chi tiết đã cho và chuyển hướng đến trang
đăng nhập
.
Mã:
const mongoose = require('mongoose')const User = require('../models/user')//log up Logicconst getSignup = async(req, res, next) => { res.render('signup')}const createUser = async (req, res, next) => { thử { const { tên, tên người dùng, mật khẩu, số điện thoại, email} = await req.body const lỗi = [] const reRenderSignup = (yêu cầu, res, tiếp theo) => { console.log(errors) res.render('signup', { errors, username, name, phonenumber, email }) } if( !name || !username || !password || !phonenumber || !email ) { errors.push({ msg: 'vui lòng điền đầy đủ thông tin vào tất cả các trường' }) reRenderSignup(req, res, next) } else { const existingUser = await User.findOne().or([{ email: email}, { phonenumber : phonenumber }]) if(existingUser) { errors.push({ msg: 'Người dùng đã tồn tại, hãy thử thay đổi email hoặc số điện thoại của bạn' }) reRenderSignup(req, res, next) } else { const user = await User.create( req.body ) req.flash( 'success_msg', 'Bạn đã đăng ký và có thể đăng nhập' ); res.redirect('/users/login') } } } catch (error) { next(error) }}module.exports = { createUser, getSignup}
loginController.js
,- Chúng tôi sử dụng phương thức
passport.authenticate()
với phạm vi cục bộ (email và mật khẩu) để kiểm tra xem người dùng có tồn tại không; - Nếu người dùng không tồn tại, chúng tôi đưa ra thông báo lỗi và chuyển hướng người dùng đến cùng một tuyến đường;
- Nếu người dùng tồn tại, chúng tôi đăng nhập người dùng bằng phương thức
req.logIn
, gửi cho họ xác minh bằng hàmsendVerification()
, sau đó chuyển hướng họ đến tuyến đườngverify
.
Mã:
const mongoose = require('mongoose')const passport = require('passport')const User = require('../models/user')const { sendVerification } = require('../config/twilioLogic')const getLogin = async(req, res) => { res.render('đăng nhập')}const authUser = async(req, res, next) => { thử { hộ chiếu.xác thực('cục bộ', hàm(lỗi, người dùng, thông tin) { nếu (lỗi) { trả về tiếp theo(lỗi) } nếu (!người dùng) { req.flash( 'error_msg', thông tin.message ) trả về res.redirect('/người dùng/đăng nhập') } req.logIn(người dùng, hàm(lỗi) { nếu (lỗi) { trả về tiếp theo(lỗi) } sendVerification(req, res, req.user.phonenumber) res.redirect('/người dùng/xác minh'); }); })(req, res, tiếp theo); } catch (error) { next(error) }}module.exports = { getLogin, authUser}
sendVerification()
không thực sự hoạt động. Đó là vì chúng ta chưa viết hàm, vì vậy chúng ta cần Twilio
cho việc đó. Hãy cài đặt Twilio và bắt đầu.Sử dụng Twilio Verify để bảo vệ các tuyến đường
Để sử dụng Twilio Verify, bạn:- Truy cập
https://www.twilio.com/
; - Tạo tài khoản với Twilio;
- Đăng nhập vào bảng điều khiển của bạn;
- Chọn tạo dự án mới;
- Thực hiện theo các bước để tạo dự án mới.
Twilio SDK
cho node.js:
Mã:
npm install twilio
dotenv
để hỗ trợ chúng ta với biến môi trường
.
Mã:
npm install dotenv
.env
. Tệp này là nơi chúng tôi lưu trữ thông tin xác thực
của mình, vì vậy chúng tôi không đẩy nó lên git. Để thực hiện điều đó, chúng tôi tạo một tệp .gitignore
trong thư mục gốc của dự án và thêm các dòng sau vào tệp:
Mã:
node_modules.env
node_modules
và tệp .env
.Để lấy thông tin xác thực tài khoản Twilio, chúng tôi đăng nhập vào bảng điều khiển Twilio và sao chép
ACCOUNT SID
và AUTH TOKEN
của mình. Sau đó, chúng ta nhấp vào lấy số dùng thử
và Twilio sẽ tạo cho chúng ta một số dùng thử, nhấp vào chấp nhận số
. Bây giờ từ bản sao bảng điều khiển, chúng ta sao chép số dùng thử của mình.Trong
.env
,Đừng quên thay thếTWILIO_ACCOUNT_SID = <YOUR_ACCOUNT_SID>
TWILIO_AUTH_TOKEN = <YOUR_AUTH_TOKEN>
TWILIO_PHONE_NUMBER = <TOUR_TWILIO_NUMBER>
,
và
bằng thông tin xác thực thực tế của bạn.Chúng ta tạo một tệp có tên
twilioLogic.js
trong config
thư mục:
Mã:
cd cofigtouch twilioLogic.js
twilioLogic.js
,
Mã:
require('dotenv').config()const twilio = require('twilio')const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN)//tạo dịch vụ xác minhconst createService = async(req, res) => { client.verify.services.create({ friendlyName: 'phoneVerification' }) .then(service => console.log(service.sid))}createService();
verify
mới.Chạy:
Mã:
node config/twilioLogic.js
TWILIO_VERIFICATION_SID
— chúng ta sao chép chuỗi đó.Trong
.env
, thêm dòng TWILIO_VERIFICATION_SID =
.Trong
config/twilioLogic.js
, chúng ta xóa dòng createService()
, vì chúng ta chỉ cần tạo dịch vụ verify
một lần. Sau đó, chúng ta thêm các dòng mã sau:
Mã:
//sau khi tạo hàm createService//gửi mã xác minhconst sendVerification = async(req, res, number) => { client.verify.services(process.env.TWILIO_VERIFICATION_SID) .verifications .create({to: `${number}`, channel: 'sms'}) .then( verification => console.log(verification.status) );}//kiểm tra mã xác minhconst checkVerification = async(req, res, number, code) => { return new Promise((resolve, reject) => { client.verify.services(process.env.TWILIO_VERIFICATION_SID) .verificationChecks .create({to: `${number}`, code: `${code}`}) .then(verification_check => { resolve(verification_check.status) }); })}module.exports = { sendVerification, checkVerification}
sendVerification
là một hàm bất đồng bộ trả về một lời hứa gửi OTP xác minh đến số được cung cấp bằng kênh sms
.checkVerification
cũng là một hàm bất đồng bộ trả về một lời hứa kiểm tra trạng thái xác minh. Nó kiểm tra xem OTP
do người dùng cung cấp có phải là OTP
giống với OTP
đã được gửi cho họ hay không.Trong
config/middleware.js
, hãy thêm nội dung sau:
Mã:
//sau khi khai báo hàm notLoggedIn//ngăn người dùng chưa xác minh truy cập vào '/dashboard'const isVerified = async(req, res, next) => { if(req.session.verified){ return next() } else { req.flash( 'error_msg', 'Bạn phải được xác minh để thực hiện điều đó' ) res.redirect('/users/login') }}//ngăn Người dùng đã xác minh truy cập vào '/verify'const notVerified = async(req, res, next) => { if(!req.session.verified){ return next() } else { res.redirect('back') }}module.exports = { //after notLoggedIn isVerified, notVerified}
phần mềm trung gian cấp tuyến đường
, sẽ được sử dụng sau trong các tuyến đường của chúng tôi.isVerified
và notVerified
để kiểm tra xem người dùng đã được xác minh hay chưa. Chúng tôi sử dụng các phần mềm trung gian này để chặn quyền truy cập vào các tuyến đường mà chúng tôi muốn chỉ cho phép người dùng đã xác minh truy cập.
Mã:
cd controllerstouch verifyController.js
verifyController.js
,
Mã:
const mongoose = require('mongoose')const passport = require('passport')const User = require('../models/user')const { sendVerification, checkVerification } = require('../config/twilioLogic')const loadVerify = async(req, res) => { res.render('verify')}const resendCode = async(req, res) => { sendVerification(req, res, req.user.phonenumber) res.redirect('/users/verify')}const verifyUser = async(req, res) => { //kiểm tra mã xác minh từ đầu vào của người dùng const verifyStatus = await checkVerification(req, res, req.user.phonenumber, req.body.verifyCode) if(verifyStatus === 'approved') { req.session.verified = true res.redirect('/users/dashboard') } else { req.session.verified = false req.flash( 'error_msg', 'mã xác minh sai' ) res.redirect('/users/verify') }}module.exports = { loadVerify, verifyUser, resendCode}
resendCode()
gửi lại mã xác minh cho người dùng.verifyUser
sử dụng hàm checkVerification
được tạo trong phần trước. Nếu trạng thái là đã phê duyệt
, chúng tôi sẽ đặt giá trị đã xác minh
trên req.session
thành đúng.req.session
chỉ cung cấp một cách hay để truy cập phiên hiện tại. Điều này được thực hiện bởi express-session, thêm đối tượng phiên vào đối tượng yêu cầu của chúng ta.Do đó, lý do tôi nói rằng hầu hết phần mềm trung gian cấp ứng dụng đều ảnh hưởng đến trạng thái ứng dụng của chúng ta (đối tượng yêu cầu và phản hồi)
Xây dựng các tuyến đường người dùng
Về cơ bản, ứng dụng của chúng ta sẽ có các tuyến đường sau:-
/user/login
: để người dùng đăng nhập; -
/user/signup
: để người dùng đăng ký; -
/user/logout
: để đăng xuất; -
/user/resend
: để gửi lại mã xác minh; -
/user/verify
: để nhập mã xác minh; -
/user/dashboard
: tuyến đường được bảo vệ bằngTwilio Xác minh
.
Mã:
cd routestouch user.js
routes/user.js
, nó yêu cầu các gói cần thiết:
Mã:
const express = require('express')const router = express.Router()const { createUser, getSignup } = require('../controllers/signUpController')const { authUser, getLogin } = require('../controllers/loginController')const { loadVerify, verifyUser, resendCode } = require('../controllers/verifyController')const { isLoggedIn, isVerified, notVerified, notLoggedIn } = require('../config/middleware')//tuyến đường đăng nhậprouter.route('/login') .all(notLoggedIn) .get(getLogin) .post(authUser)//tuyến đường đăng nhậprouter.route('/signup') .all(notLoggedIn) .get(getSignup) .post(createUser)//logoutrouter.route('/logout') .get(async (req, res) => { req.logout(); res.redirect('/'); })router.route('/resend') .all(isLoggedIn, notVerified) .get(resendCode)//verify routerouter.route('/verify') .all(isLoggedIn, notVerified) .get(loadVerify) .post(verifyUser)//dashboardrouter.route('/dashboard') .all(isLoggedIn, isVerified) .get(async (req, res) => { res.render('dashboard') })//export routermodule.exports = router
router.route()
chỉ định tuyến đường. Nếu chúng ta chỉ định router.route('/login')
, chúng ta sẽ nhắm mục tiêu đến tuyến đường login
. .all([middleware])
cho phép chúng ta chỉ định rằng tất cả các yêu cầu đến tuyến đường đó phải sử dụng các middleware
đó.Cú pháp
router.route('/login').all([middleware]).get(getController).post(postController)
là một giải pháp thay thế cho cú pháp mà hầu hết các nhà phát triển đã quen sử dụng.Nó thực hiện cùng một chức năng như
router.get('/login', [middleware], getController)
và router.post('/login, [middleware], postController)
.Bây giờ, nếu chúng ta chạy ứng dụng của mình bằng cách nhập lệnh bên dưới vào thiết bị đầu cuối của mình:Cú pháp được sử dụng trong mã của chúng ta rất hay vì nó làm cho mã của chúng ta rất DRY — và dễ dàng theo dõi những gì đang diễn ra trong tệp của chúng ta hơn.
Mã:
npm run dev
Kết luận
Những gì chúng ta đã làm trong hướng dẫn này là:- Xây dựng một ứng dụng express;
- Thêm hộ chiếu để xác thực bằng các phiên;
- Sử dụng Twilio Verify để bảo vệ tuyến đường.
Những gì bạn có thể làm tiếp theo:
- Thử khám phá hộ chiếu, sử dụng JWT để xác thực;
- Tích hợp những gì bạn đã học ở đây vào một ứng dụng khác;
- Khám phá thêm các sản phẩm Twilio. Họ cung cấp các dịch vụ giúp phát triển dễ dàng hơn (Verify chỉ là một trong nhiều dịch vụ).
Đọc thêm về Tạp chí Smashing
- “Cách xây dựng ứng dụng trò chuyện nhóm bằng Vanilla JS, Twilio và Node.js,” Zara Cooper
- “Giữ cho Node.js nhanh: Công cụ, kỹ thuật và mẹo để tạo máy chủ Node.js hiệu suất cao,” David Mark Clements
- “Cách bảo vệ khóa API của bạn trong quá trình sản xuất bằng tuyến API Next.js,” Caleb Olojo
- “Cách xây dựng API Node.js cho chuỗi khối Ethereum,” John Agbanusi