Ngày xưa lúc mình mới tiếp cận với Nodejs và đọc các tutorial hướng dẫn trêng mạng, mình luôn phải vật lộn với việc hiểu phần Authentication của nó. Thay vì thực sự giải thích cơ chế và những gì đang xảy ra, mình chỉ cảm thấy như tác giả chỉ đơn giản là cung cấp một hướng dẫn về cách sao chép/dán từ tài liệu. Bài viết này nhằm thực sự hướng dẫn bạn qua quy trình authentication và giải thích từng cơ chế một.
LỜI KHUYÊN: Bạn NÊN vừa đọc vừa đối chiêu với code nếu có thể thì hãy code nó ra là tốt nhất. Việc đó giẽ giúp bạn hiểu hơn khi đọc giải thích. Nếu chỉ đọc giài thích có thể bạn sẽ cảm thấy có lúc rất khó hiểu.
Điều kiện tiền quyết:mình giả sử như các bạn đã có thế sử dụng cơ bản với Terminal/command-line interface(CLI) và Javascript/Node.js.
Bước 1. Thêm endpoint login
Đầu tiên, chúng ta sẽ thêm một route login vào ứng dụng của mình với cả hàm GET và POST. Lưu ý rằng trong hàm POST, chúng ta đang gọi 'req.body'. Điều này sẽ ghi lại dữ liệu mà chúng ta gửi đến server trong request POST của chúng ta.
const express = require('express');
const { v4:uuid } = require("uuid");
const session = require('express-session')
const FileStore = require('session-file-store')(session);
const app = express();
app.use(session({
genid:(req) => {
console.log('Inside the session middleware')
console.log(req.sessionID)
return uuid()
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
app.get('/',(req, res) => {
console.log('Inside the homepage callback function')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
app.get('/login',(req, res) => {
console.log('Inside GET /login callback function')
console.log(req.sessionID)
res.send(`You got the login page!\n`)
})
app.post('/login',(req, res) => {
console.log('Inside POST /login callback function')
console.log(req.body)
res.send(`You posted to the login page!\n`)
})
app.listen(3000,() => {
console.log('Listening on localhost:3000')
})
Bước 2. Configure Express để có thể đọc dữ liệu từ POST
Trong terminal client của chúng ta, chạy một lệnh cURL mới.
curl -X POST http://localhost:3000/login -b cookie-file.txt -H 'Content-Type:application/json' -d '{"email":"test@test.com", "password":"password"}'
Lưu ý, nếu bạn đang sử dụng máy Windows, bạn sẽ cần sử dụng dấu ngoặc kép và sử dụng gạch chéo ngược để phân biệt chúng, như sau:
curl -X POST http://localhost:3000/login -b cookie-file.txt -H "Content-Type:application/json" -d "{\"email\":\"test@test.com\", \"password\":\"password\"}"
Mình sẽ chỉ sử dụng các dấu ngoặc kép cho phần còn lại của bài viết này vì nó dễ đọc hơn. Chỉ cần nhớ trên Windows, bạn cần sử dụng dấu ngoặc kép và escape nó bằng dẫu chéo ngược.
Được rồi hãy quay lại nào. Ở trên, chúng ta đã thay đổi một vài điều.
-
Chúng ta hiện đang sử dụng
-X POST thay vì -X GET
-
Chúng ta đã thêm
flag -H để đặt loại nội dung headers thành application/json
-
Chúng ta thêm
flag -d cùng với dữ liệu mà chúng ta muốn gửi. Lưu ý rằng nó nằm trong dấu ngoặc kép
Nhìn vào output server của chúng ta, chúng ta thấy:
Inside POST /login callback function
undefined
Có vẻ như req.body là 'undefined'. Vấn đề ở đây là Express không thực sự biết cách đọc nội dung JSON, vì vậy chúng ta cần thêm một middleware khác để thực hiện việc này. Chúng ta có thể sử dụng middleware body-parser để phân tích cú pháp cơ thể dữ liệu và thêm nó vào thuộc tính req.body.. Chúng ta lại cài đặt nó thôi.
server $ npm install body-parser --save
Ngoài lề:Dùng Nodejs rất nhẹ nhàng nhất là Express vì nó chả có cái gì cả. Cần gì thì cứ dùng Middleware nấy là xong.
Sau đó, require nó trong server.js và configure express để sử dụng nó.
const express = require('express');
const { v4:uuid } = require("uuid")
const session = require('express-session')
const FileStore = require('session-file-store')(session);
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({ extended:false }))
app.use(bodyParser.json())
app.use(session({
genid:(req) => {
console.log('Inside the session middleware')
console.log(req.sessionID)
return uuid()
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
app.get('/',(req, res) => {
console.log('Inside the homepage callback function')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
app.get('/login',(req, res) => {
console.log('Inside GET /login callback function')
console.log(req.sessionID)
res.send(`You got the login page!\n`)
})
app.post('/login',(req, res) => {
console.log('Inside POST /login callback function')
console.log(req.body)
res.send(`You posted to the login page!\n`)
})
app.listen(3000,() => {
console.log('Listening on localhost:3000')
})
Bạn sẽ nhận thấy ở trên rằng khi chúng ta định configure ứng dụng của mình để sử dụng middleware phân tích cú pháp body-parser, bodyParser.json() và bodyParser.urlencoded(). Trong khi chúng ta gửi dữ liệu của mình trực tiếp đến server ở định dạng JSON.
Cài đặt mô-đun passport.js cùng với mô-đun authentication passport-local strategy.
server $ npm install passport passport-local --save
Trước khi chúng ta đi vào code, hãy nói về quy trình authentication. (Đọc có khi sẽ có chút khó hiểu vì bạn vẫn chưa mường tượng được nó. Nhưng ko sao bí quyết là bạn đọc qua 1 lần sau đó nhìn vào source code và đọc lại sẽ hiểu rõ nó hơn. Ở đây mình chỉ Overview qua thôi)
-
User sẽ
POST thông tin login của họ lên route /login
-
Chúng ta cần làm gì đó với dữ liệu đó. Đây là nơi
passport xuất hiện. Chúng ta có thể gọi passport.authenticate(‘login strategy’, callback(err, user, info)). Hàm này nhận 2 tham số. 'Strategy login' của chúng ta là 'local' trong trường hợp này, vì chúng ta sẽ authentication bằng email và password (bạn có thể tìm thấy danh sách các strategy login khác bằng passport. Chúng bao gồm Facebook, Twitter, v.v.) và function callback cấp cho chúng ta quyền truy cập vào đối tượng user nếu authentication thành công và đối tượng error nếu không thành công .
-
passport.authenticate() sẽ gọi strategy authentication 'local' của chúng ta, vì vậy chúng ta cần configure passport để sử dụng strategy đó. Chúng ta có thể configure passport bằng passport.use(new strategyClass). Ở đây, chúng ta cho passport biết cách sử dụng strategy local để authentication user.
-
Bên trong khai báo
StrategyClass, chúng ta sẽ lấy dữ liệu từ request POST của chúng ta, sử dụng dữ liệu đó để tìm user phù hợp trong cơ sở dữ liệu và kiểm tra xem thông tin login có khớp không. Nếu chúng khớp, passport sẽ thêm hàm login() vào đối tượng request của chúng ta và chúng ta sẽ gọi hàm callback passport.authenticate().
-
Bên trong hàm callback
passport.authenticate(), chúng ta gọi hàm req.login().
-
Hàm
req.login(user, callback()) nhận đối tượng user mà chúng ta vừa trả về từ strategy local của mình và gọi passport.serializeUser(callback()). Nó nhận đối tượng user đó và
-
Lưu user id vào session file store
-
Lưu user id trong request object as request.session.passport
-
Thêm user object yêu request object as request.user .
-
Bây giờ, trên các request tiếp theo đến các
route sẽ được authorized, chúng ta có thể truy xuất đối tượng user mà không cần request user login lại (bằng cách lấy id từ session file store và sử dụng id đó để lấy đối tượng user từ cơ sở dữ liệu và thêm nó vào đối tượng request của chúng ta ).
OK! Đó có lẽ là quá nhiều cho phần giải thích này rồi! Mọi thứ sẽ rõ ràng hơn sau khi xem code và ouput log của server.
const express = require('express');
const { v4:uuid } = require("uuid")
const session = require('express-session')
const FileStore = require('session-file-store')(session);
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const users = [
{id:'2f24vvg', email:'test@test.com', password:'password'}
]
passport.use(new LocalStrategy(
{ usernameField:'email' },
(email, password, done) => {
console.log('Inside local strategy callback')
const user = users[0]
if(email === user.email && password === user.password) {
console.log('Local strategy returned true')
return done(null, user)
}
}
));
passport.serializeUser((user, done) => {
console.log('Inside serializeUser callback. User id is save to the session file store here')
done(null, user.id);
});
const app = express();
app.use(bodyParser.urlencoded({ extended:false }))
app.use(bodyParser.json())
app.use(session({
genid:(req) => {
console.log('Inside session middleware genid function')
console.log(`Request object sessionID from client:${req.sessionID}`)
return uuid()
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
app.use(passport.initialize());
app.use(passport.session());
app.get('/', (req, res) => {
console.log('Inside the homepage callback')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
app.get('/login', (req, res) => {
console.log('Inside GET /login callback')
console.log(req.sessionID)
res.send(`You got the login page!\n`)
})
app.post('/login', (req, res, next) => {
console.log('Inside POST /login callback')
passport.authenticate('local', (err, user, info) => {
console.log('Inside passport.authenticate() callback');
console.log(`req.session.passport:${JSON.stringify(req.session.passport)}`)
console.log(`req.user:${JSON.stringify(req.user)}`)
req.login(user, (err) => {
console.log('Inside req.login() callback')
console.log(`req.session.passport:${JSON.stringify(req.session.passport)}`)
console.log(`req.user:${JSON.stringify(req.user)}`)
return res.send('You were authenticated & logged in!\n');
})
})(req, res, next);
})
app.listen(3000, () => {
console.log('Listening on localhost:3000')
})
OK. Có vẻ như chúng ta đã thêm một loạt code ở đây, vì mình đã thêm rất nhiều console.log để giễ dàng hơn trong việc phân tích code. (Các bạn vừa đọc phần giải thích vừa nhìn vào code từng bước từng bước 1 chứ nếu chỉ đọc giải thích ko sẽ rất khó hiểu)
-
Ở đầu, chúng ta
request passport và strategy local passport.
-
Đi xuống giữa file, chúng ta có thể thấy rằng chúng ta định
configure ứng dụng của mình để sử dụng passport làm middleware với các lệnh gọi đến app.use(passport.initialize()) và app.use(passport.session()). Lưu ý rằng chúng ta gọi những hàm này sau khi chúng ta configure ứng dụng của mình để sử dụng express-session và session-file-store. Điều này là do passport sẽ dựa trên những thứ này để hoạt động.
-
Đi sâu xuống, chúng ta thấy hàm
app.post('login') của chúng ta ngay lập tức gọi passport.authenticate() với strategy local.
-
Strategy local được configure ở đầu tệp với passport.use(new LocalStrategy()). Strategy local sử dụng tên user và password để authentication user; Tuy nhiên, vì mặc định của Strategy local sử dụng địa chỉ email thay vì user, vì vậy ở đây chúng ta chỉ đặt bí danh trường user là 'email'. Sau đó, chúng ta cho strategy local biết cách tìm và xác định user trong cơ sở dữ liệu. Ở đây, thông thường bạn sẽ thấy một cái gì đó giống như 'DB.findById()' nhưng bây giờ chúng ta sẽ bỏ qua điều đó và giả định rằng user chính xác được trả lại cho chúng ta bằng cách gọi array user của chúng ta chứa đối tượng user duy nhất của chúng ta. Lưu ý, trường 'email' và 'password' được chuyển vào hàm bên trong LocalStrategy() mới là email và password mà chúng ta gửi đến server cùng với request POST của chúng ta. Nếu dữ liệu chúng ta nhận được từ request POST khớp với dữ liệu chúng ta tìm thấy trong cơ sở dữ liệu của mình, chúng ta gọi là done(error object, user object) và truyền vào null và đối tượng user được trả về từ cơ sở dữ liệu. (Chúng ta sẽ đảm bảo xử lý các trường hợp thông tin authentication không khớp ở cuối bài này các bạn yên tâm.)
-
Sau khi hàm
done() được gọi, chúng ta đi tới hàm callback passport.authenticate(), nơi chúng ta chuyển đối tượng user vào hàm req.login() (hãy nhớ rằng, lệnh gọi tới passport.authenticate() đã thêm thông tin login() hàm đối với đối tượng request của chúng ta (nếu bạn đã đọc 1 bài viết của mình về Factory Design Pattern thì sẽ hoàn toàn hiểu làm cách nào có thể gắn login vào request - còn nếu bạn ko biết cũng ko sao
). Hàm req.login() xử lý việc tuần tự hóa id user vào session store và bên trong đối tượng request của chúng ta và cũng thêm đối tượng user vào đối tượng request.
-
Cuối cùng,
respond trả về rằng "You were authenticated & logged in!"
Chúng ta cùng thử nhé! Gọi request cURL và gửi thông tin login của chúng ta đến server. Lưu ý, trước khi thực hiện thao tác bên dưới, mình đã xóa tất cả các tệp được lưu trữ trong thư mục /session của mình và mình đang gọi request POST với flag '-c' để tạo/ghi đè cookie-file.txt trong thư mục client của chúng ta. Vì vậy, về cơ bản chúng ta đang bắt đầu với tất cả mọi thứ đã sạch sẽ.
client $ curl -X POST http://localhost:3000/login -c cookie-file.txt -H 'Content-Type:application/json' -d '{"email":"test@test.com", "password":"password"}'
You were authenticated & logged in!
Trong ouput log server, bạn sẽ thấy một cái gì đó như sau.
Inside session middleware genid function
Request object sessionID from client:undefined
Inside POST /login callback
Inside local strategy callback
Local strategy returned true
Inside passport.authenticate() callback
req.session.passport:undefined
req.user:undefined
Inside serializeUser callback. User id is save to the session file store here
Inside req.login() callback
req.session.passport:{"user":"2f24vvg"}
req.user:{"id":"2f24vvg","email":"test@test.com","password":"password"}
Như bạn thấy ở trên, trước khi chúng ta gọi req.login(), đối tượng req.session.passport và đối tượng req.user là undefined. Sau khi hàm req.login() được gọi (tức là khi chúng ta đang ở trong hàm callback req.login()), chúng đã được xác định và in ra log tương ứng!
Bước 4. Thêm requires authorization (yếu cầu phải được ủy quyền)
Hãy thêm một route vào ứng dụng của chúng ta đó là requires authorization. Lưu ý, bây giờ user đã được 'authentication' (tức là đã login), chúng ta có thể nói về 'authorization' cho server cho các route nào request user login trước khi họ có thể được truy cập vào logic để lấy dữ liện bên trong.
const express = require('express');
const { v4:uuid } = require("uuid")
const session = require('express-session')
const FileStore = require('session-file-store')(session);
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const users = [
{id:'2f24vvg', email:'test@test.com', password:'password'}
]
passport.use(new LocalStrategy(
{ usernameField:'email' },
(email, password, done) => {
console.log('Inside local strategy callback')
const user = users[0]
if(email === user.email && password === user.password) {
console.log('Local strategy returned true')
return done(null, user)
}
}
));
passport.serializeUser((user, done) => {
console.log('Inside serializeUser callback. User id is save to the session file store here')
done(null, user.id);
});
passport.deserializeUser((id, done) => {
console.log('Inside deserializeUser callback')
console.log(`The user id passport saved in the session file store is:${id}`)
const user = users[0].id === id ? users[0] :false;
done(null, user);
});
const app = express();
app.use(bodyParser.urlencoded({ extended:false }))
app.use(bodyParser.json())
app.use(session({
genid:(req) => {
console.log('Inside session middleware genid function')
console.log(`Request object sessionID from client:${req.sessionID}`)
return uuid()
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
app.use(passport.initialize());
app.use(passport.session());
app.get('/', (req, res) => {
console.log('Inside the homepage callback')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
app.get('/login', (req, res) => {
console.log('Inside GET /login callback')
console.log(req.sessionID)
res.send(`You got the login page!\n`)
})
app.post('/login', (req, res, next) => {
console.log('Inside POST /login callback')
passport.authenticate('local', (err, user, info) => {
console.log('Inside passport.authenticate() callback');
console.log(`req.session.passport:${JSON.stringify(req.session.passport)}`)
console.log(`req.user:${JSON.stringify(req.user)}`)
req.login(user, (err) => {
console.log('Inside req.login() callback')
console.log(`req.session.passport:${JSON.stringify(req.session.passport)}`)
console.log(`req.user:${JSON.stringify(req.user)}`)
return res.send('You were authenticated & logged in!\n');
})
})(req, res, next);
})
app.get('/authrequired', (req, res) => {
console.log('Inside GET /authrequired callback')
console.log(`User authenticated? ${req.isAuthenticated()}`)
if(req.isAuthenticated()) {
res.send('you hit the authentication endpoint\n')
} else {
res.redirect('/')
}
})
app.listen(3000, () => {
console.log('Listening on localhost:3000')
})
Bạn có thể thấy ở trên, chúng ta đã thêm một route '/authrequired' có sẵn thông qua hàm get để kiểm tra đối tượng request của chúng ta để xem liệu req.isAuthenticated() có đúng không. Đây là function được thêm vào đối tượng request của chúng ta bằng passport (Xem qua để hiểu cách thêm Factory design pattern). Hãy tạo một session mới bằng cách truy cập home, sau đó sử dụng session mới đó để thử chuyển sang route /authrequired.
Lưu ý, trong request thứ hai của bên dưới, chúng ta đang dùng flag '-L', flag này request cURL tuân thủ theo các chuyển hướng trong logic của chúng ta.
client $ curl -X GET http://localhost:3000 -c cookie-file.txt
You got home page!
client $ curl -X GET http://localhost:3000/authrequired -b cookie-file.txt -L
You got home page!
Bây giờ, hãy kiểm tra ouput log của server.
#first request to the home page
Inside session middleware genid function
Request object sessionID from client: undefined
Inside the homepage callback
e6388389-0248-4c69-96d1-fda44fbc8839
#second request to the /authrequired route
Inside GET /authrequired callback
User authenticated? false
Bạn có thể thấy ở trên rằng chúng ta chưa bao giờ sử dụng hàm callback của passport.deserializeUser(), vì chúng ta chưa authentication. Bây giờ, hãy truy cập lại route login và sử dụng cookie-file.txt hiện có của chúng ta, sau đó truy cập vào route /authrequired.
curl -X POST http://localhost:3000/login -b cookie-file.txt -H 'Content-Type: application/json' -d '{"email":"test@test.com", "password":"password"}'
You were authenticated & logged in!
curl -X GET http://localhost:3000/authrequired -b cookie-file.txt -L
you hit the authentication endpoint
Lần này, bạn có thể thấy chúng ta nhận được thông báo "you hit the authentication endpoint" Trong ouput log server, chúng ta thấy:
Inside POST /login callback
Inside local strategy callback
Local strategy returned true
Inside passport.authenticate() callback
req.session.passport: undefined
req.user: undefined
Inside serializeUser callback. User id is save to the session file store here
Inside req.login() callback
req.session.passport: {"user":"2f24vvg"}
req.user: {"id":"2f24vvg","email":"test@test.com","password":"password"}
Inside deserializeUser callback
The user id passport saved in the session file store is: 2f24vvg
Inside GET /authrequired callback
User authenticated? true
Một điều mới cần chỉ ra ở đây là chúng ta đã nhận được hàm callback deserializeUser, hàm này khớp id session của chúng ta với session-file-store và truy xuất id user của chúng ta.
Bước 5. Kết nối cơ sở dữ liệu và xử lý thông tin login không chính xác
Đây sẽ là một bước tiến lớn nữa! Đầu tiên, hãy tạo một thư mục khác bên trong authTuts có tên là 'db', khởi tạo npm và cài đặt json-server và tạo một tệp db.json mới.
authTuts $ mkdir db
authTuts $ cd db
db $ npm init -y
db $ npm install json-server --save
db $ touch db.json
Sau khi cài đặt xong json-server, hãy thêm tập lệnh “json:server” mới vào package.json của nó (db/package.json):
{
"name": "db",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"json:server": "json-server --watch ./db.json --port 5000"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"json-server": "^0.17.0"
}
}
Vậy mục đích của tất cả những điều chúng ta làm này giờ là gì? json-server là một package tự động thiết lập các route RESTful cho dữ liệu trong tệp db.json. Hãy thử sao chép/dán phần sau vào tệp db.json của bạn.
{
"users": [
{
"id":"2f24vvg",
"email": "test@test.com",
"password": "password"
},
{
"id":"d1u9nq",
"email": "user2@example.com",
"password": "password"
}
]
}
Sau đó gọi 'npm run json:server' từ thư mục /db.
db $ npm run json:server
Sau đó, mở http://localhost:5000/user trong trình duyệt của bạn. Bạn sẽ thấy JSON từ tệp db.json của chúng ta đang được xuất hiện. Hãy thử truy cập vào một route /user cụ thể:http://localhost:5000/users/2f24vvg. Bạn sẽ chỉ thấy id, email và password của user đó. Hãy thử lại lần nữa, nhưng thay vì chuyển trực tiếp id user vào URL, hãy chuyển địa chỉ email dưới dạng tham số truy vấn tới URL:http://localhost:5000/users? Email=user2@example.com. Lần này, bạn sẽ nhận được đối tượng JSON của user thứ hai của chúng ta. Khá tuyệt, phải không?! Một Fake API hoàn hảo.\
Trong thực tế cũng vậy mô hình Microservice chúng ta sẽ có nhiều RESTful API khác nhau cho các feature khác nhau. Và hiện tại chúng ta cứ xem như phần CRUD (Thêm xóa sửa User và quản lý user) một bên khác đang làm và mình tạm thời Mock nó bằng cái Fake API này.
Để server json chạy trong tab riêng của nó trong terminal và hãy lật lại tab Terminal của chúng ta trong thư mục server (hoặc mở một cái mới nếu bạn cần) và hãy cài đặt axios, một gói giúp tìm nạp dữ liệu. (fetch data)
server $ npm install axios --save
Trong configure LocalStrategy của chúng ta, bây giờ chúng ta sẽ tìm fetch đối tượng user của chúng ta từ endpoint REST/users bằng cách sử dụng địa chỉ email làm tham số truy vấn (giống như chúng ta đã làm theo cách thủ công trước đây). Trong khi đó cũng cập nhật configure của chúng ta để xử lý thông tin login user không hợp lệ hoặc bất kỳ error nào được trả về bởi axios từ server json.
Trong hàm passport.deserializeUser() của chúng ta, hãy trả về đối tượng user bằng cách gọi axios để truy xuất endpoint /users và chuyển id user trong đường dẫn (tức là /users/:id).
Hãy cũng xử lý các error khác nhau có thể xuất hiện trong quá trình authentication trong hàm callbackpassport.authenticate() của chúng ta và thay vì chỉ cho user biết rằng họ đã login, hãy chuyển hướng user đến đường dẫn /authrequired.
const express = require('express');
const { v4: uuid } = require("uuid")
const session = require('express-session')
const FileStore = require('session-file-store')(session);
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const axios = require('axios');
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(email, password, done) => {
axios.get(`http://localhost:5000/users?email=${email}`)
.then(res => {
const user = res.data[0]
if (!user) {
return done(null, false, { message: 'Invalid credentials.\n' });
}
if (password != user.password) {
return done(null, false, { message: 'Invalid credentials.\n' });
}
return done(null, user);
})
.catch(error => done(error));
}
));
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
axios.get(`http://localhost:5000/users/${id}`)
.then(res => done(null, res.data) )
.catch(error => done(error, false))
});
const app = express();
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(session({
genid: (req) => {
return uuid()
},
store: new FileStore(),
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use(passport.initialize());
app.use(passport.session());
app.get('/', (req, res) => {
res.send(`You got home page!\n`)
})
app.get('/login', (req, res) => {
res.send(`You got the login page!\n`)
})
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if(info) {return res.send(info.message)}
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.login(user, (err) => {
if (err) { return next(err); }
return res.redirect('/authrequired');
})
})(req, res, next);
})
app.get('/authrequired', (req, res) => {
if(req.isAuthenticated()) {
res.send('you hit the authentication endpoint\n')
} else {
res.redirect('/')
}
})
app.listen(3000, () => {
console.log('Listening on localhost:3000')
})
Như bạn có thể thấy ở trên, mình đã xóa tất cả code dùng để ouput log server của chúng ta. Vì bây giờ chúng ta đã hiểu quy trình authentication, nên tất cả việc logging đó là không cần thiết. Có khá nhiều code mới ở trên, nhưng mình nghĩ rằng nó sẽ khá dễ dàng để hiểu những gì đang xảy ra. Hầu hết chúng ta chỉ thêm câu lệnh 'if' để xử lý bất kỳ error nào được throw ra.
Hãy thử gọi endpoint login với request CURL POST. Lưu ý, mình đã bỏ flag '-X POST' vì chúng ta muốn cURL đi theo chuyển hướng từ route /login đến route /authrequired mà chúng ta GET được. Nếu chúng ta để flag '-X POST' thì nó cũng sẽ cố gắng POST lên route /authrequired. Thay vào đó, chúng ta sẽ chỉ để cURL phán đoán nó sẽ làm gì trên mỗi route.
client $ curl http://localhost:3000/login -c cookie-file.txt -H 'Content-Type: application/json' -d '{"email":"test@test.com", "password":"password"}' -L
you hit the authentication endpoint
Bước 6. Xử lý mã hóa password
Đầu tiên, hãy cài đặt bcrypt trên server của chúng ta.
server $ npm install bcrypt-nodejs --save
Bây giờ chúng ta require nó trong tệp server.js và gọi nó trong configure LocalStrategy, nơi chúng ta khớp thông tin login mà user gửi với thông tin login được lưu trên chương trình phụ trợ. Đầu tiên, bạn nhập password bạn nhận được từ user, password này phải là văn bản thuần túy và đối số thứ 2 là password được băm (hash) và được lưu trữ trong cơ sở dữ liệu.
const express = require('express');
const { v4: uuid } = require("uuid")
const session = require('express-session')
const FileStore = require('session-file-store')(session);
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const axios = require('axios');
const bcrypt = require('bcrypt-nodejs');
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(email, password, done) => {
axios.get(`http://localhost:5000/users?email=${email}`)
.then(res => {
const user = res.data[0]
if (!user) {
return done(null, false, { message: 'Invalid credentials.\n' });
}
if (!bcrypt.compareSync(password, user.password)) {
return done(null, false, { message: 'Invalid credentials.\n' });
}
return done(null, user);
})
.catch(error => done(error));
}
));
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
axios.get(`http://localhost:5000/users/${id}`)
.then(res => done(null, res.data) )
.catch(error => done(error, false))
});
const app = express();
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(session({
genid: (req) => {
return uuid()
},
store: new FileStore(),
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use(passport.initialize());
app.use(passport.session());
app.get('/', (req, res) => {
res.send(`You got home page!\n`)
})
app.get('/login', (req, res) => {
res.send(`You got the login page!\n`)
})
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if(info) {return res.send(info.message)}
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.login(user, (err) => {
if (err) { return next(err); }
return res.redirect('/authrequired');
})
})(req, res, next);
})
app.get('/authrequired', (req, res) => {
if(req.isAuthenticated()) {
res.send('you hit the authentication endpoint\n')
} else {
res.redirect('/')
}
})
app.listen(3000, () => {
console.log('Listening on localhost:3000')
})
Bây giờ chúng ta chỉ cần đảm bảo rằng chúng ta đã lưu trữ password hash trong cơ sở dữ liệu. Bạn có thể sử dụng công cụ này để hash 'password' và lưu trữ các value password trong tệp 'db.json'.
{
"users": [
{
"id":"2f24vvg",
"email": "test@test.com",
"password": "$2a$12$nv9iV5/UNuV4Mdj1Jf8zfuUraqboSRtSQqCmtOc4F7rdwmOb9IzNu"
},
{
"id":"d1u9nq",
"email": "user2@example.com",
"password": "$2a$12$VHZ9aJ5A87YeFop4xVW.aOMm95ClU.EviyT9o0i8HYLdG6w6ctMfW"
}
]
}
Cuối cùng, hãy thử login lại.
client $ curl http://localhost:3000/login -c cookie-file.txt -H 'Content-Type: application/json' -d '{"email":"test@test.com", "password":"password"}' -L
you hit the authentication endpoint
Yeah
You did it!
Hy vọng rằng bạn đã biết thêm một chút về những điều sau:
-
Express và cách nó sử dụng middleware
-
Cách dữ liệu
session được lưu trữ và truy xuất cả trên server và client
-
Quy trình
authentication của passport và cách sử dụng nó để authorization
-
Cách sử dụng
bcrypt để kiểm tra dựa trên password đã hash.
Mình sẽ để thêm quy trình POST Signup như một bài tập cho bạn. Và đây là một gợi ý nhỏ: hãy kiểm tra để đảm bảo rằng không có user có địa chỉ email đó đã có trong cơ sở dữ liệu, nếu không có, bạn có thể sử dụng axios.post() để lưu trữ dữ liệu trong db.json(đảm bảo password đã được hash bằng bcrypt), sau đó gọi req.login() với đối tượng user mà bạn đã tạo.
Và cuối cùng, hãy luôn tham khảo tài liệu nếu bạn đang tìm kiếm thêm thông tin!
Như mọi khi, mình hy vọng bạn thích bài viết này và biết thêm được điều gì đó mới.
Cảm ơn và hẹn gặp lại các bạn trong những bài viết tiếp theo! 😍