Xác thực người dùng trong NodeJS bằng Passport và MongoDB
Nếu bạn muốn bảo vệ nội dung nhạy cảm trong ứng dụng Node của mình, bạn cần có cách xác thực người dùng. Tuy nhiên, việc xây dựng hệ thống xác thực của riêng bạn rất phức tạp, tốn nhiều thời gian và nếu không được thực hiện đúng cách có thể tạo ra các lỗ hổng bảo mật trong ứng dụng của bạn. Các công cụ của bên thứ ba như Passport giúp xác thực dễ dàng hơn.
Trong hướng dẫn này, bạn sẽ học cách triển khai xác thực trong Node bằng Passport và MongoDB.
Mục Lục
Xác thực và Ủy quyền là gì?
Trong khi xác thực và ủy quyền đôi khi được sử dụng thay thế cho nhau, hai khái niệm bảo mật này có ý nghĩa khác nhau. Xác thực là quá trình xác minh người dùng là người mà họ tuyên bố trong khi ủy quyền là quá trình xác định xem người dùng đã xác thực có quyền truy cập vào các phần nhất định của ứng dụng của bạn hay không.
Passport.js là gì?
Passport.js (hoặc Passport) là một phần mềm trung gian xác thực cho NodeJS cung cấp hơn 500 chiến lược để xác thực người dùng bao gồm hộ chiếu-địa phương sử dụng tên người dùng và mật khẩu.
Hướng dẫn này sử dụng hộ chiếu-địa phương và passport-jwt đến các tuyến đường an toàn.
Cách thiết lập xác thực người dùng trong NodeJS
Bây giờ bạn đã biết một chút về xác thực người dùng và Passport.js, chúng ta có thể xem cách thiết lập xác thực trên NodeJS. Dưới đây, chúng tôi đã trình bày các bước bạn cần thực hiện.
Bước 1: Thiết lập máy chủ nút
Tạo một thư mục có tên user-auth-nodejs và điều hướng đến nó bằng cách sử dụng thiết bị đầu cuối của bạn.
mkdir user-auth-nodejscd user-auth-nodejs
Khởi tạo tiếp theo gói.json.
npm init
Vì bạn sẽ sử dụng Express, một khung phụ trợ NodeJS, hãy cài đặt nó bằng cách chạy lệnh sau.
npm i express
Bây giờ hãy tạo một tệp, app.jsvà thêm mã sau để tạo máy chủ.
const express = require("express");
const app = express();
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
Bước 2: Thiết lập cơ sở dữ liệu
Bạn cần một cơ sở dữ liệu để lưu trữ dữ liệu người dùng. Bạn sẽ sử dụng mongoose để tạo một lược đồ dữ liệu MongoDB xác định cấu trúc và kiểu dữ liệu bạn sẽ lưu trữ trong cơ sở dữ liệu. Vì bạn đang lưu trữ dữ liệu người dùng, hãy tạo một lược đồ người dùng.
Cài đặt mongoose.
npm i mongoose
Tạo một tệp mới, userModel.jsvà thêm phần sau.
const mongoose = require('mongoose')
const {Schema} = mongoose
const UserSchema = new Schema ({
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
})
const UserModel = mongoose.model('user', UserSchema);
module.exports = UserModel;
Trước khi lưu trữ mật khẩu, bạn cần mã hóa nó cho mục đích bảo mật. Bạn sẽ sử dụng bcryptjsmột gói npm rất hữu ích giúp làm việc với mật khẩu được mã hóa dễ dàng.
Cài đặt bcryptjs.
npm i bcryptjs
Biến đổi usermodel.js để mã hóa mật khẩu trước khi lưu vào cơ sở dữ liệu.
const mongoose = require('mongoose')
const bcrypt = require('bcryptjs');
const {Schema} = mongooseconst UserSchema = new Schema ({
...
})
UserSchema.pre('save', async function(next) {
try {
// check method of registration
const user = this;
if (!user.isModified('password')) next();
// generate salt
const salt = await bcrypt.genSalt(10);
// hash the password
const hashedPassword = await bcrypt.hash(this.password, salt);
// replace plain text password with hashed password
this.password = hashedPassword;
next();
} catch (error) {
return next(error);
}
});
...
const User = mongoose.model('User', UserSchema);
Ở đây bạn đang sử dụng một lưu trước hook để sửa đổi mật khẩu trước khi nó được lưu. Ý tưởng là lưu trữ phiên bản băm của mật khẩu thay vì mật khẩu văn bản thuần túy. Hàm băm là một chuỗi dài phức tạp được tạo ra từ một chuỗi văn bản thuần túy.
Sử dụng đã được sửa đổi để kiểm tra xem mật khẩu có đang thay đổi hay không vì bạn chỉ cần băm mật khẩu mới. Tiếp theo, tạo một muối và chuyển nó với mật khẩu văn bản thuần túy vào phương thức băm để tạo mật khẩu băm. Cuối cùng, thay thế mật khẩu văn bản thuần túy bằng mật khẩu băm trong cơ sở dữ liệu.
Tạo db.js và cấu hình cơ sở dữ liệu.
const mongoose = require("mongoose");
mongoose.Promise = global.Promise;
const dbUrl = "mongodb://localhost/user";
const connect = async () => {
mongoose.connect(dbUrl, { useNewUrlParser: true, useUnifiedTopology: true });
const db = mongoose.connection;
db.on("error", () => {
console.log("could not connect");
});
db.once("open", () => {
console.log("> Successfully connected to database");
});
};
module.exports = { connect };
Trong app.js, hãy kết nối với cơ sở dữ liệu.
// connect to db
const db = require('./db');
db.connect();
Bước 3: Thiết lập Hộ chiếu
Cài đặt Hộ chiếu và hộ chiếu-địa phương. Bạn sẽ sử dụng các gói này để đăng ký và người dùng đăng nhập.
npm i passport
npm i passport-local
Tạo một tệp mới, passportConfig.jsvà nhập khẩu hộ chiếu-địa phương và userModel.js.
const LocalStraregy = require("passport-local").Strategy;
const User = require("./userModel");
Cấu hình Hộ chiếu để xử lý đăng ký người dùng.
const LocalStrategy = require("passport-local");
const User = require("./userModel");
module.exports = (passport) => {
passport.use(
"local-signup",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
// check if user exists
const userExists = await User.findOne({ "email": email });
if (userExists) {
return done(null, false)
}
// Create a new user with the user data provided
const user = await User.create({ email, password });
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
}
Trong đoạn mã trên, bạn đang kiểm tra xem email đã được sử dụng chưa. Nếu email không tồn tại, hãy đăng ký người dùng. Lưu ý rằng bạn cũng đang đặt trường tên người dùng để chấp nhận email. Theo mặc định, hộ chiếu-địa phương mong đợi một tên người dùng, vì vậy bạn cần cho nó biết bạn đang chuyển qua email.
Sử dụng hộ chiếu-địa phương để xử lý thông tin đăng nhập của người dùng.
module.exports = (passport) => {
passport.use(
"local-signup",
new localStrategy(
...
)
);
passport.use(
"local-login",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
const user = await User.findOne({ email: email });
if (!user) return done(null, false);
const isMatch = await user.matchPassword(password);
if (!isMatch)
return done(null, false);
// if passwords match return user
return done(null, user);
} catch (error) {
console.log(error)
return done(error, false);
}
}
)
);
};
Tại đây, hãy kiểm tra xem người dùng có tồn tại trong cơ sở dữ liệu hay không và nếu họ có, hãy kiểm tra xem mật khẩu được cung cấp có khớp với mật khẩu trong cơ sở dữ liệu hay không. Lưu ý rằng bạn cũng gọi matchPassword () trên mô hình người dùng, vì vậy hãy đi tới userModel.js tập tin và thêm nó.
UserSchema.methods.matchPassword = async function (password) {
try {
return await bcrypt.compare(password, this.password);
} catch (error) {
throw new Error(error);
}
};
Phương thức này so sánh mật khẩu từ người dùng và mật khẩu trong cơ sở dữ liệu và trả về true nếu chúng khớp nhau.
Bước 4: Thiết lập các tuyến xác thực
Bây giờ bạn cần tạo các điểm cuối mà người dùng sẽ gửi dữ liệu. Đầu tiên là lộ trình đăng ký sẽ chấp nhận email và mật khẩu của người dùng mới.
Ở trong app.jssử dụng phần mềm trung gian xác thực hộ chiếu mà bạn vừa tạo để đăng ký người dùng.
app.post(
"/auth/signup",
passport.authenticate('local-signup', { session: false }),
(req, res, next) => {
// sign up
res.json({
user: req.user,
});
}
);
Nếu thành công, tuyến đăng ký sẽ trả về người dùng đã tạo.
Tiếp theo, tạo lộ trình đăng nhập.
app.post(
"/auth/login",
passport.authenticate('local-login', { session: false }),
(req, res, next) => {
// login
res.json({
user: req.user,
});
}
);
Bước 5: Thêm các tuyến đường được bảo vệ
Cho đến nay, bạn đã sử dụng Hộ chiếu để tạo một phần mềm trung gian đăng ký người dùng trong cơ sở dữ liệu và một phần mềm khác cho phép người dùng đã đăng ký đăng nhập. Tiếp theo, bạn sẽ tạo một phần mềm trung gian ủy quyền để bảo vệ các tuyến đường nhạy cảm bằng cách sử dụng mã thông báo web JSON (JWT). Để triển khai ủy quyền JWT, bạn cần:
- Tạo mã thông báo JWT.
- Chuyển mã thông báo cho người dùng. Người dùng sẽ gửi lại nó trong các yêu cầu ủy quyền.
- Xác minh mã thông báo được người dùng gửi lại.
Bạn sẽ sử dụng jsonwebtoken gói để xử lý JWT.
Chạy lệnh sau để cài đặt nó.
npm tôi jsonwebtoken
Tiếp theo, tạo mã thông báo cho mỗi người dùng đăng nhập thành công.
Ở trong app.jsnhập khẩu jsonwebtoken và sửa đổi lộ trình đăng nhập như bên dưới.
app.post(
"/auth/login",
passport.authenticate('local-login', { session: false }),
(req, res, next) => {
// login
jwt.sign({user: req.user}, 'secretKey', {expiresIn: '1h'}, (err, token) => {
if(err) {
return res.json({
message: "Failed to login",
token: null,
});
}
res.json({
token
});
})
}
);
Trong một ứng dụng đời thực, bạn sẽ sử dụng một khóa bí mật phức tạp hơn và lưu trữ nó trong một tệp cấu hình.
Lộ trình đăng nhập trả về một mã thông báo nếu thành công.
Sử dụng passport-jwt đến truy cập các tuyến đường được bảo vệ.
npm i passport-jwt
Ở trong passportConfig.jscấu hình passport-jwt.
const JwtStrategy = require("passport-jwt").Strategy;
const { ExtractJwt } = require("passport-jwt")
module.exports = (passport) => {
passport.use(
"local-login",
new LocalStrategy(
...
);
passport.use(
new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromHeader("authorization"),
secretOrKey: "secretKey",
},
async (jwtPayload, done) => {
try {
// Extract user
const user = jwtPayload.user;
done(null, user);
} catch (error) {
done(error, false);
}
}
)
);
};
Lưu ý rằng bạn đang trích xuất JWT từ tiêu đề ủy quyền thay vì nội dung yêu cầu. Điều này ngăn chặn tin tặc chặn một yêu cầu và lấy mã thông báo.
Để xem làm thế nào passport-jwt bảo vệ các tuyến đường, tạo một tuyến đường được bảo vệ trong app.js.
app.get(
"/user/protected",
passport.authenticate("jwt", { session: false }),
(req, res, next) => {
res.json({user: req.user});
}
);
Chỉ một yêu cầu có JWT hợp lệ mới trả về dữ liệu người dùng.
Bây giờ bạn đã sẵn sàng để nâng cấp xác thực người dùng của mình lên cấp độ tiếp theo
Trong hướng dẫn này, bạn đã học cách xác thực người dùng bằng email và mật khẩu với sự trợ giúp của Passport. Thoạt nghe có vẻ khó khăn, nhưng quá trình này tương đối đơn giản. Bạn có thể tiến xa hơn nữa và sử dụng các nhà cung cấp danh tính bên thứ ba được Passport hỗ trợ như Twitter, Facebook và Google.
Đọc tiếp
Thông tin về các Tác giả