Tất cả những gì bạn cần để xây dựng một Node.js Server & Authentication (Cơ bản): Express, Sessions, Passport, and cURL - Part 2/2 😊 (Series: Bí kíp Javascript - PHẦN 21)
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
.
PHẦN 2 - Authentication (Các bạn tham khảo Phần 1 ở đây nhé)
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.
//npm modules
const express = require('express');
const { v4:uuid } = require("uuid");
const session = require('express-session')
const FileStore = require('session-file-store')(session);
//create the server
const app = express();
//add & configure middleware
app.use(session({
genid:(req) => {
console.log('Inside the session middleware')
console.log(req.sessionID)
return uuid() //use UUIDs for session IDs
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
//create the homepage route at '/'
app.get('/',(req, res) => {
console.log('Inside the homepage callback function')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
//create the login get and post routes
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`)
})
//tell the server what port to listen on
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 dungheaders
thànhapplication/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ó.
//npm modules
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');
//create the server
const app = express();
//add & configure middleware
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() //use UUIDs for session IDs
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
//create the homepage route at '/'
app.get('/',(req, res) => {
console.log('Inside the homepage callback function')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
//create the login get and post routes
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`)
})
//tell the server what port to listen on
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
.
Bước 3. Add và configure Passport.js
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ọipassport.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ằngemail
vàpassword
(bạn có thể tìm thấy danh sách cácstrategy
login
khác bằngpassport
. Chúng bao gồmFacebook
,Twitter
, v.v.) vàfunction callback
cấp cho chúng ta quyền truy cập vào đối tượnguser
nếuauthentication
thành công và đối tượngerror
nếu không thành công . passport.authenticate()
sẽ gọistrategy authentication
'local
' của chúng ta, vì vậy chúng ta cần configurepassport
để sử dụngstrategy
đó. Chúng ta có thểconfigure passport
bằngpassport.use(new strategyClass)
. Ở đây, chúng ta chopassport
biết cách sử dụngstrategy 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ìmuser
phù hợp trongcơ sở dữ liệu
và kiểm tra xem thông tinlogin
có khớp không. Nếu chúng khớp,passport
sẽ thêm hàmlogin()
vào đối tượngrequest
của chúng ta và chúng ta sẽ gọi hàm callbackpassport.authenticate()
. - Bên trong hàm callback
passport.authenticate()
, chúng ta gọi hàmreq.login()
. - Hàm
req.login(user, callback())
nhận đối tượnguser
mà chúng ta vừa trả về từstrategy local
của mình và gọipassport.serializeUser(callback())
. Nó nhận đối tượnguser
đó 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ẽ đượcauthorized
, chúng ta có thể truy xuất đối tượnguser
mà không cầnrequest user login
lại (bằng cách lấyid
từsession file store
và sử dụngid
đó để lấy đối tượnguser
từcơ sở dữ liệu
và thêm nó vào đối tượngrequest
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
.
//npm modules
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'}
]
//configure passport.js to use the local strategy
passport.use(new LocalStrategy(
{ usernameField:'email' },
(email, password, done) => {
console.log('Inside local strategy callback')
//here is where you make a call to the database
//to find the user based on their username or email address
//for now, we'll just pretend we found that it was users[0]
const user = users[0]
if(email === user.email && password === user.password) {
console.log('Local strategy returned true')
return done(null, user)
}
}
));
//tell passport how to serialize the user
passport.serializeUser((user, done) => {
console.log('Inside serializeUser callback. User id is save to the session file store here')
done(null, user.id);
});
//create the server
const app = express();
//add & configure middleware
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() //use UUIDs for session IDs
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
app.use(passport.initialize());
app.use(passport.session());
//create the homepage route at '/'
app.get('/', (req, res) => {
console.log('Inside the homepage callback')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
//create the login get and post routes
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);
})
//tell the server what port to listen on
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ụngpassport
làmmiddleware
với các lệnh gọi đếnapp.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ụngexpress-session
vàsession-file-store
. Điều này là dopassport
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ọipassport.authenticate()
vớistrategy local
. Strategy local
đượcconfigure
ở đầu tệp vớipassport.use(new LocalStrategy())
.Strategy local
sử dụng tênuser
vàpassword
đểauthentication user
; Tuy nhiên, vì mặc định củaStrategy local
sử dụng địa chỉemail
thay vìuser
, vì vậy ở đây chúng ta chỉ đặt bí danh trườnguser
là 'email
'. Sau đó, chúng ta chostrategy local
biết cách tìm và xác địnhuser
trongcơ 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ằnguser
chính xác được trả lại cho chúng ta bằng cách gọiarray user
của chúng ta chứa đối tượnguser
duy nhất của chúng ta. Lưu ý, trường 'email
' và 'password
' được chuyển vào hàm bên trongLocalStrategy()
mới làemail
vàpassword
mà chúng ta gửi đến server cùng vớirequest 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 trongcơ sở dữ liệu
của mình, chúng ta gọi làdone(error object, user object)
và truyền vàonull
và đối tượnguser
đượ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 tinauthentication
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 callbackpassport.authenticate()
, nơi chúng ta chuyển đối tượnguser
vào hàmreq.login()
(hãy nhớ rằng, lệnh gọi tớipassport.authenticate()
đã thêm thông tinlogin()
hàm đối với đối tượngrequest
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ắnlogin
vàorequest
- còn nếu bạn ko biết cũng ko sao ). Hàmreq.login()
xử lý việc tuần tự hóaid user
vàosession store
và bên trong đối tượngrequest
của chúng ta và cũng thêm đối tượnguser
vào đối tượngrequest
. - 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.
//npm modules
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'}
]
//configure passport.js to use the local strategy
passport.use(new LocalStrategy(
{ usernameField:'email' },
(email, password, done) => {
console.log('Inside local strategy callback')
//here is where you make a call to the database
//to find the user based on their username or email address
//for now, we'll just pretend we found that it was users[0]
const user = users[0]
if(email === user.email && password === user.password) {
console.log('Local strategy returned true')
return done(null, user)
}
}
));
//tell passport how to serialize the 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);
});
//create the server
const app = express();
//add & configure middleware
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() //use UUIDs for session IDs
},
store:new FileStore(),
secret:'keyboard cat',
resave:false,
saveUninitialized:true
}))
app.use(passport.initialize());
app.use(passport.session());
//create the homepage route at '/'
app.get('/', (req, res) => {
console.log('Inside the homepage callback')
console.log(req.sessionID)
res.send(`You got home page!\n`)
})
//create the login get and post routes
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('/')
}
})
//tell the server what port to listen on
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
.
//npm modules
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');
// configure passport.js to use the local strategy
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));
}
));
// tell passport how to serialize the user
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))
});
// create the server
const app = express();
// add & configure middleware
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(session({
genid: (req) => {
return uuid() // use UUIDs for session IDs
},
store: new FileStore(),
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use(passport.initialize());
app.use(passport.session());
// create the homepage route at '/'
app.get('/', (req, res) => {
res.send(`You got home page!\n`)
})
// create the login get and post routes
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('/')
}
})
// tell the server what port to listen on
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
.
//npm modules
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');
// configure passport.js to use the local strategy
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));
}
));
// tell passport how to serialize the user
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))
});
// create the server
const app = express();
// add & configure middleware
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(session({
genid: (req) => {
return uuid() // use UUIDs for session IDs
},
store: new FileStore(),
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use(passport.initialize());
app.use(passport.session());
// create the homepage route at '/'
app.get('/', (req, res) => {
res.send(`You got home page!\n`)
})
// create the login get and post routes
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('/')
}
})
// tell the server what port to listen on
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ụngmiddleware
- Cách dữ liệu
session
được lưu trữ và truy xuất cả trênserver
vàclient
- Quy trình
authentication
củapassport
và cách sử dụng nó đểauthorization
- Cách sử dụng
bcrypt
để kiểm tra dựa trênpassword
đã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.
Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.
Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.
Momo: NGUYỄN ANH TUẤN - 0374226770
TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)
All rights reserved