Tuesday, January 19, 2021

เรียนรู้การใช้ JWT กับ Passport.js Authentication

 

Image for post

Passport.js

Passport is Simple, unobtrusive authentication for Node.js

ตามคำนิยามแปลได้ว่า “การยืนยันตัวตนที่ง่ายและไม่ยุ่งยาก สำหรับ Node.js”

JWT

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

JWT ย่อมาจาก JSON Web Token เป็นมาตรฐานเปิด (RFC 7519) ที่เข้ามา claims ความปลอดภัยของการส่งข้อมูลระหว่างสองฝ่าย โดยที่ถูกออกแบบไว้ว่า จะต้องมีขนาดที่กระทัดรัด (Compact) และเก็บข้อมูลภายในตัว (Self-contained)

  1. Setup dependencies
  2. Setup local passport strategy
  3. Using passport authenticate & Sign JWT
  4. Using passport middleware for route user

เริ่มกันเลย!!!

ต่อไปนี้จะเป็นการสาธิตการใช้งาน Passport และ JWT สำหรับสร้างการยืนยันตัวตนแบบง่าย ด้วย email/password โดยสามารถค่อยๆทำตามทีละ step ได้เลยครับ และตอนท้ายจะมี source code แจกให้ไว้ทำความเข้าใจอีกทีครับ ไม่พูดพร่ำทำเพลงมาก ลุยโล๊ดดด++

Project Directory Structure

passport-jwt/
+-- configs
¦ +-- passport.js
+-- routes
¦ +-- auth.js
¦ +-- users.js
+-- app.js
+-- package.json

Setup Simple API with Express

ติดตั้ง express ด้วยคำสั่งต่อไปนี้

yarn add express
// app.jsconst express = require('express'),
app = express(),
port = process.env.PORT || 3000
// Set Parses JSON
app.use(express.json())
// Error Handler
app.use((err, req, res, next) => {
let statusCode = err.status || 500
res.status(statusCode)
res.json({
error: {
status: statusCode,
message: err.message
}
});
});
// Start Server
app.listen(port, ()=>console.log(`Server is running on port ${port}`))

Setup dependencies

ติดตั้ง dependencies ทั้งหมดที่ต้องใช้

yarn add passport passport-local passport-jwt jsonwebtoken
  • passport-local: สำหรับการยืนยันตัวตนด้วย email/password
  • passport-jwt: สำหรับการยืนยันตัวตนด้วย JSON web token
  • jsonwebtoken: สำหรับจัดการเกี่ยวกับ Token
  • Client จะเก็บ token ไว้ (ส่วนใหญ่จะเก็บไว้ใน localStorage) และเมื่อมี request ก็จะส่งมาด้วยทุกครั้ง เพื่อใช้ในการยืนยันตัวตน
  • ทุก request จะผ่าน middleware เพื่อนำ token ที่ส่งมา มาตรวจสอบเพื่อยืนยันตัวตนและจะอนุญาตเฉพาะ request ที่ยืนยันตัวตนสำเร็จเท่านั้น

Setup local passport strategy

เริ่มจากการ setup การใช้ local passport strategy สำหรับกำหนดเงื่อนไขการตรวจสอบการยืนยันตัวตนด้วย email/password

// configs/passport.jsconst passport = require('passport’),
LocalStrategy = require('passport-local').Strategy
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
(email, password, cb) => {
//this one is typically a DB call. Assume that the returned user object is pre-formatted and ready for storing in JWT

return UserModel.findOne({email, password})
.then(user => {
if (!user) {
return cb(null, false, {message: 'Incorrect email or password.'})
}
return cb(null, user, {message: 'Logged In Successfully'}) })
.catch(err => cb(err))
}
));
// app.jsconst express = require('express'),
app = express(),
port = process.env.PORT || 3000
// Set Parses JSON
app.use(express.json())
// Import passport
require('./configs/passport');
...

Using passport authenticate & Sign JWT

ต่อมาเราจะมาเขียนโค้ดฟังก์ชันสำหรับการ login ในไฟล์ routes/auth.js โดยจะมีการเรียกใช้ฟังก์ชัน passport.authentication(‘local’) ซึ่งฟังก์ชันนี้ใช้สำหรับการตรวจสอบการยืนยันตัวตนของ user ด้วยวิธีการแบบ local strategy นั้นคือใช้ email/password และมีการ handle error หากข้อมูลไม่ถูกต้องหรือเกิดข้อผิดพลาด

// routes/auth.jsconst router = require('express').Router(),
jwt = require('jsonwebtoken')
passport = require('passport')
/* POST login. */
router.post('/login', (req, res, next) => {

passport.authenticate('local', {session: false}, (err, user, info) => {
if (err) return next(err) if(user) {
const token = jwt.sign(user, 'your_jwt_secret')
return res.json({user, token})
} else {
return res.status(422).json(info)
}
})(req, res, next);});

แนวคิดคือ สำหรับเก็บข้อมูลที่น้อยที่สุดที่สามารถใช้ได้ โดยไม่ต้องดึงข้อมูลจากฐานข้อมูลในทุกครั้งที่ยืนยันตัวตน

ในส่วนถัดมาเราจะทำการสร้าง middleware สำหรับตรวจสอบ Token สำหรับ request ที่ต้องการเข้าถึง path url ที่ต้องมีการยืนยันตัวตนก่อน เช่น /user/profile เป็นต้น ซึ่งส่วนนี้เราจะใช้ passport-jwt strategy กันครับ โดยให้เพิ่มโค้ดในไฟล์ /configs/passport.js

// configs/passport.js...
const passportJWT = require("passport-jwt"),
JWTStrategy = passportJWT.Strategy,
ExtractJWT = passportJWT.ExtractJwt
...
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'your_jwt_secret'
},
(jwtPayload, cb) => {
//find the user in db if needed. This functionality may be omitted if you store everything you'll need in JWT payload.

return UserModel.findOneById(jwtPayload.id)
.then(user => {
return cb(null, user);
})
.catch(err => {
return cb(err);
});
}
));
Image for post
// routes/user.jsconst express = require('express').Router()

/* GET users listing. */
router.get('/', (req, res, next) => {
res.send('respond with a resource');
});

/* GET user profile. */
router.get('/profile', (req, res, next) => {
res.send(req.user);
});

module.exports = router;

Using passport middleware for route user

และขั้นตอนสุดท้าย ทำการเพิ่ม route auth และ route user ในไฟล์ /app.js และเพิ่มการเรียกใช้ passport authentication middleware ให้กับ route user (/user/*) เพื่อตรวจสอบการยืนยันตัวตนทุกครั้งที่มี Request

// app.js...const auth = require('./routes/auth');
const user = require('./routes/user');
app.use('/auth', auth)
app.use('/user', passport.authenticate('jwt', {session: false}), user)
...

ทดสอบกับ Postman

Image for post
POST /auth/login
Image for post
GET /user/profile

JWT helps to organize authentication without storing the authentication state in any storage be it a session or a database

บทความสำหรับสร้าง Config Middleware for Passport Authentication

No comments:

Post a Comment