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)
Bài đăng này đã không được cập nhật trong 2 năm
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 POSTthay vì-X GET
- Chúng ta đã thêm flag -Hđể đặt loại nội dungheadersthànhapplication/json
- Chúng ta thêm flag -dcù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ẽ POSTthô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 passportxuấ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ằngemailvàpassword(bạn có thể tìm thấy danh sách cácstrategyloginkhác bằngpassport. Chúng bao gồmFacebook,Twitter, v.v.) vàfunction callbackcấp cho chúng ta quyền truy cập vào đối tượngusernếuauthenticationthành công và đối tượngerrornế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 passportbằng- passport.use(new strategyClass). Ở đây, chúng ta cho- passportbiế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 POSTcủa chúng ta, sử dụng dữ liệu đó để tìmuserphù hợp trongcơ sở dữ liệuvà kiểm tra xem thông tinlogincó khớp không. Nếu chúng khớp,passportsẽ thêm hàmlogin()vào đối tượngrequestcủ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ượngusermà chúng ta vừa trả về từstrategy localcủ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 routesẽ đượcauthorized, chúng ta có thể truy xuất đối tượngusermà không cầnrequest user loginlại (bằng cách lấyidtừsession file storevà sử dụngidđó để lấy đối tượngusertừcơ sở dữ liệuvà thêm nó vào đối tượngrequestcủ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 passportvà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ụngpassportlàmmiddlewarevớ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-sessionvàsession-file-store. Điều này là dopassportsẽ 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được- configureở đầu tệp với- passport.use(new LocalStrategy()).- Strategy localsử dụng tên- uservà- passwordđể- authentication user; Tuy nhiên, vì mặc định của- Strategy localsử dụng địa chỉ- emailthay vì- user, vì vậy ở đây chúng ta chỉ đặt bí danh trường- userlà '- email'. Sau đó, chúng ta cho- strategy localbiết cách tìm và xác định- usertrong- 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- userchính xác được trả lại cho chúng ta bằng cách gọi- array usercủa chúng ta chứa đối tượng- userduy 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à- emailvà- passwordmà chúng ta gửi đến server cùng với- request POSTcủa chúng ta. Nếu dữ liệu chúng ta nhận được từ- request POSTkhớp với dữ liệu chúng ta tìm thấy trong- cơ sở dữ liệucủa mình, chúng ta gọi là- done(error object, user object)và truyền vào- nullvà đố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- authenticationkhô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ượnguservà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ượngrequestcủ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ắnloginvàorequest- còn nếu bạn ko biết cũng ko sao ). Hàm ). Hàmreq.login()xử lý việc tuần tự hóaid uservàosession storevà bên trong đối tượngrequestcủa chúng ta và cũng thêm đối tượnguservào đối tượngrequest.
- Cuối cùng, respondtrả 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!
 You did it!
Hy vọng rằng bạn đã biết thêm một chút về những điều sau:
- Expressvà cách nó sử dụng- middleware
- Cách dữ liệu sessionđược lưu trữ và truy xuất cả trênservervàclient
- Quy trình authenticationcủapassportvà 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
 
  
 
