Skip to main content
เทคโนโลยี

สร้างระบบ api authentication ด้วย JWT Refresh token กับ NodeJs กัน

สวัสดีครับทุกคน วันนี้เราจะมาพาทุกคนมาลงมือทำระบบ authentication ด้วย JWT Refresh token บน NodeJS กัน

ผู้เขียน Thapanon S.- BorntoDev Co., Ltd.

แต่ก่อนอื่นเราต้องมาทำความรู้จัก  JWT และ Concept ของ JWT Refresh token กันก่อน

ผมเชื่อว่าหลายๆคนในที่นี้คงจะรู้จัก JWT หรือ Json web token กันแล้ว ดังนั้นเราก็จะมาสรุปกันแบบสั้น ๆ ให้กับคนที่อาจจะยังไม่รู้มาก่อน

โดยต้องบอกก่อนเลยว่า JWT นั้นเป็นท่ามาตรฐานที่ใช้กันทั่วไปในการทำ Web Authentication โดย JWT จะเป็น json ที่ประกอบไปด้วย 3 ส่วน ได้แก่ Header,Payload และ Signature โดยในแต่ละส่วนจะถูกคั่นด้วย . แบบนี้เลย Header.Payload.Signature แล้วหลักการทำงานของ jwt ก็จะเป็นตาม diagram ด้านล่างเลย และสำหรับใครที่อยากได้ความรู้เกี่ยวกับ JWT แบบเต็มๆ ก็สามารถอ่านต่อได้ที่ บทความ

และจาก Diagram ก็จะเห็นได้ว่าถ้า Access token นั้น Expire หรือถูก Reject ผู้ใช้จะต้อง Login ใหม่
และต่อไปเราก็จะมาเข้าสู่หัวข้อหลักของวันนี้แล้วนั่นคือ JWT Refresh token โดย Concept ของ JWT Refresh token  ก็จะเหมือนกับ JWT เลยแต่มีขั้นตอนพิเศษเพิ่มขึ้นมา ตาม Diagram ด้านล่างเลย

 

จาก Diagram ด้านบน จะเห็นได้ว่าเมื่อผู้ใช้ Login ก็จะได้ Token ไป 2 ตัว นั่นก็คือ Access token กับ Refresh token โดยทั้ง 2 ตัวนี้ก็จะเป็น JWT ทั้งคู่แต่ทำหน้าที่แตกต่างกันโดย

Access token มีหน้าที่อย่างที่เรารู้ ๆ กันนั่นก็คือเอาไว้เพื่อยืนยันตัวตนในการรับข้อมูลที่ถูกป้องกันไว้ 

แต่ Refresh token จะเอาไว้ใช้เพื่อขอ Access token ใหม่เมื่อของเก่านั้น Expire หรือถูก Reject โดยเมื่อ Access token นั่นใช้ไม่ได้เราก็จะเขียน Code ให้หน้าบ้านนั้นนำ Refresh token มาแลกกับ Access token ใหม่ที่จะเอาไปใช้ในการขอข้อมูลในครั้งต่อ ๆ ไปนั่นเอง

ต่อไปเราจะมาลองลงมือเขียน Code กันนน

“`mkdir example-jwt-refresh-token“`

“`cd example-jwt-refresh-token“`

“`npm init -y “`

“`npm i dotenv express jsonwebtoken“`

แล้วหลังจากนั้นเราก็จะสร้าง file index.js มาและใส่ code ชุดนี้ลงไป

const express = require("express")
const jwt = require("jsonwebtoken")
require("dotenv").config()

const app = express()

app.use(express.json())

const port = process.env.PORT || 3000

const users = [
  { id: 1, name: "John", refresh: null },
  { id: 2, name: "Tom", refresh: null },
  { id: 3, name: "Chris", refresh: null },
  { id: 4, name: "David", refresh: null },
]

app.get("/", (req, res) => {
  res.send("Hello World!")
})

app.listen(port, () => {
  console.log(`app listening on port ${port}`)
})

และก็สร้าง file .env

ACCESS_TOKEN_SECRET=EiKf9vBVMW0Qiu6EWgzwU7PyCdD0BLxv7ks4kTe4fXvGPDYsS3QT3wugV4ReGopt
REFRESH_TOKEN_SECRET=0ueUlWRDDjvu7188rORSqZVuwWUVvJSyPGWw84J3HxgWmW9VKRP4RFzW2Imvb1Jr
PORT=80

แล้วเราก็รันตัว file index.js ที่พึ่งเขียนไปเมื่อสักครู่นี้เราก็จะได้ API server ที่สามารถรันได้แล้ว

ต่อไปเราจะสร้าง End point ขึ้นมาเพิ่มอีก 2 จุดได้แก่

POST /auth/login

POST /auth/refresh

เราจะมาเริ่มที่ login กันก่อน โดยในตัวอย่างผมจะไม่ใช้ database แต่จะใช้เป็นค่าจากตัวแปรแทน

โดย end point นี้ เราจะให้ผู้ใช้กรอก name มาเพื่อสร้าง JWT และให้เราสร้าง function มาเพื่อ generate token เอาไว้ก่อนโดยให้ Arguments ที่เราใส่เข้าไปเป็น Object ของ user : { id: number , name: string }

app.post("/auth/login", (req, res) => {
  const { name } = req.body

//find user 
  const user = users.findIndex((e) => e.name === name)

  if (!name || user < 0) {
    return res.send(400)
  }

  const access_token = jwtGenerate(users[user])
  const refresh_token = jwtRefreshTokenGenerate(users[user])

  users[user].refresh = refresh_token

  res.json({
    access_token,
    refresh_token,
  })
})

ให้เราพัก End point ไว้ก่อน แล้วมาทำ Function สำรับสร้าง Access token กับ Refresh token กัน

const jwtGenerate = (user) => {
  const accessToken = jwt.sign(
    { name: user.name, id: user.id },
    process.env.ACCESS_TOKEN_SECRET,
    { expiresIn: "3m", algorithm: "HS256" }
  )

  return accessToken
}

const jwtRefreshTokenGenerate = (user) => {
  const refreshToken = jwt.sign(
    { name: user.name, id: user.id },
    process.env.REFRESH_TOKEN_SECRET,
    { expiresIn: "1d", algorithm: "HS256" }
  )

  return refreshToken
}

จาก Code function ด้านบน จะเห็นได้ว่าเราใช้ Lib ที่มีชื่อว่า jsonwebtoken ในการสร้าง Token ออกมาแต่ผมจะทำการตั้งค่าให้อายุการใช้งานและ Secret key ที่ต่างกัน โดย Access token มีระยะเวลา 3 นาที และ refresh token มีอายุ 1 วัน โดยระยะเวลาก็ต้องมาตัดสินใจตามความเหมาะสมอีกทีนะครับ

ต่อไปเราจะมาทำ ?iddleware สำหรับ Validate JWT กัน

const jwtValidate = (req, res, next) => {
  try {
    if (!req.headers["authorization"]) return res.sendStatus(401)

    const token = req.headers["authorization"].replace("Bearer ", "")

    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, decoded) => {
      if (err) throw new Error(error)
    })
    next()
  } catch (error) {
    return res.sendStatus(403)
  }
}

จาก Validate Function จะเห็นได้ว่าผมได้เอา “Bearer “ ออกและเอาแต่ token มาเพื่อให้ jsonwebtoken นั่นไป Validate ว่า JWT ของเรานั้นถูกต้องไหมและเรายังสามารถตรวจสอบใน Database เพิ่มเติมได้ว่ามี User นั่นจริงๆ ไหม

หลังจากนั้นก็เอา Function ไปไว้ในส่วนที่เราต้องการจะป้องกัน

app.get("/", jwtValidate, (req, res) => {
  res.send("Hello World!")
})

หลังจากนั้นก็ลอง รันแบบไม่ใส่ Token และแบบใส่ Token ดู

ผลการรันแบบใส่ Token

ผลการรันแบบไม่ใส่ Token

และเมื่อผ่านไป 3 นาที Access token ของเราก็จะหมดอายุ และใช้ขอข้องมูลไม่ได้อีกต่อไป

ต่อมาเราจะมาเขียน end point /auth/refresh ไว้ใช้สำหรับให้เราเอา Refresh token มาแลกกับ Token ชุดใหม่

ก่อนอื่นเราจะมาทำ function สำหรับ Validate Refresh token กัน

const jwtRefreshTokenValidate = (req, res, next) => {
  try {
    if (!req.headers["authorization"]) return res.sendStatus(401)
    const token = req.headers["authorization"].replace("Bearer ", "")

    jwt.verify(token, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => {
      if (err) throw new Error(error)

      req.user = decoded
      req.user.token = token
      delete req.user.exp
      delete req.user.iat
    })
    next()
  } catch (error) {
    return res.sendStatus(403)
  }
}
}

ในการ Validate Refresh token ผมจะทำงานคล้ายๆ กับ Validate แต่จะแทรก user ไปกับ req ด้วย เพื่อนำไปใช้ Validate user ต่อ ถัดมาเราก็จะมาเขียนตรง End point กัน

app.post("/auth/refresh", jwtRefreshTokenValidate, (req, res) => {
  const user = users.find(
    (e) => e.id === req.user.id && e.name === req.user.name
  )

  const userIndex = users.findIndex((e) => e.refresh === req.user.token)

  if (!user || userIndex < 0) return res.sendStatus(401)

  const access_token = jwtGenerate(user)
  const refresh_token = jwtRefreshTokenGenerate(user)
  users[userIndex].refresh = refresh_token

  return res.json({
    access_token,
    refresh_token,
  })
})

ใน Code จะเห็นได้ว่าหลังจะที่ Refresh token นั่น Validate ผ่านแล้วจะมีการเอาข้อมูลของ user นั้นมา Generate Accress token และ Refresh token ตัวใหม่และทำการเก็บ Refresn token ตัวใหม่ไว้แทนแล้วส่ง token ชุดใหม่กลับไปให้ หน้าฝั่งหน้าบ้างของเราเก็บนั่นเอง มาลองยิง api กัน

จะเห็นได้ว่าเราได้ Token ชุดใหม่มาแล้วเมื่อเรายิง API เพื่อของ Token ชุดใหม่ด้วย Refresh token เก่า ผลที่ออกมาก็คือ จะไม่สามารถใช้งานได้นั่นเอง

ต่อมาเราลองเอา Access token ใหม่มายิง API กัน

ก็จะเห็นได้ว่าเราสามารถใช้งานได้แบบปกติเลย

 

เป็นยังไงบ้างครับทุกคนหวังว่าจะได้ความรู้และเข้าใจ Concept ของ JWT และ JWT Refresh token กันนะครับ

หากคุณสนใจพัฒนา สตาร์ทอัพ แอปพลิเคชัน
และ เทคโนโลยีของตัวเอง ?

อย่ารอช้า ! เรียนรู้ทักษะด้านดิจิทัลเพื่ออัพเกรดความสามารถของคุณ
เริ่มตั้งแต่พื้นฐาน พร้อมปฏิบัติจริงในรูปแบบหลักสูตรออนไลน์วันนี้

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

    ประเภทของคุกกี้มีความจำเป็นสำหรับการทำงานของเว็บไซต์ เพื่อให้คุณสามารถใช้ได้อย่างเป็นปกติ และเข้าชมเว็บไซต์ คุณไม่สามารถปิดการทำงานของคุกกี้นี้ในระบบเว็บไซต์ของเราได้
    รายละเอียดคุกกี้

  • คุกกี้สำหรับการติดตามทางการตลาด

    ประเภทของคุกกี้ที่มีความจำเป็นในการใช้งานเพื่อการวิเคราะห์ และ นำเสนอโปรโมชัน สินค้า รวมถึงหลักสูตรฟรี และ สิทธิพิเศษต่าง ๆ คุณสามารถเลือกปิดคุกกี้ประเภทนี้ได้โดยไม่ส่งผลต่อการทำงานหลัก เว้นแต่การนำเสนอโปรโมชันที่อาจไม่ตรงกับความต้องการ
    รายละเอียดคุกกี้

บันทึกการตั้งค่า