# 🏅 Day 28 - JWT 產生身份驗證 token 當使用者登入或註冊,後端經過驗證後確認資料格式正確,就會產生一組的 token 回傳至使用者(client 端),此 token 用於身份驗證,接下來 client 端造訪需權限的頁面、發送需要權限的請求,若未帶上此 token 或是 token 驗證錯誤,都會請求失敗 接下來會使用 [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken)(JWT `JSON Web Token`) 套件中的 `sign()` 實作產生 token 的流程(流程圖步驟 2) ```javascript const jwt = require('jsonwebtoken'); jwt.sign(payload, secretOrPrivateKey, [options, callback]) ``` - payload 會是一個物件,為該使用者的相關資訊(如 `id`) - secretOrPrivateKey 私鑰可以是字串、buffer 或物件,這裡會加入一段字串(如:`secret`)並設定為環境變數 `process.env.JWT_SECRET` - options 或 callback 函式,options 為一個物件,可根據文件中有提供的選項客製化,例如:設定此 token 的到期日 ```javascript const token = jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_DAY }); ``` 產生出的 token 會以 `.` 分隔,其中第一二段的 header 及 payload 都是可以透過 base64 編碼得知內容的 ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. # header eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. # payload TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ # signature ``` ![](https://i.imgur.com/C2fj19V.png) > [JWT 流程圖](https://whimsical.com/jwt-UKUY1rj1vfoN6uyic7e4Sm) <br> ### 參考資源 - [什麼是 JWT](https://5xruby.tw/posts/what-is-jwt) - [jwt.sign()](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) 題目 --- 延續前一天,優化登入功能,運用 `jwt.sign()` 產生 JWT,當使用者成功登入時回傳 token 與使用者名稱 name ```javascript= router.post('/sign_in', handleErrorAsync(async (req, res, next) => { const { email, password } = req.body; if (!email || !password) { next(appError(400, '帳號密碼不可為空', next)); } const user = await User.findOne({ email }).select('+password'); const auth = await bcrypt.compare(password, user.password); if (!auth) { next(appError(400, '帳號或密碼錯誤,請重新輸入!', next)); } const token = ... res.status(200).json({ status: 'success', user: { token, name: user.name } }); })); ``` ## 回報流程 將答案寫在 CodePen 並複製 CodePen 連結貼至底下回報就算完成了喔! 解答位置請參考下圖(需打開程式碼的部分觀看) ![](https://i.imgur.com/vftL5i0.png) <!-- 解答: ```javascript= router.post('/sign_in', handleErrorAsync(async (req, res, next) => { const { email, password } = req.body; if (!email || !password) { next(appError(400, '帳號密碼不可為空', next)); } const user = await User.findOne({ email }).select('+password'); const auth = await bcrypt.compare(password, user.password); if (!auth) { next(appError(400, '帳號或密碼錯誤,請重新輸入!', next)); } const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_DAY }); // JWT_SECRET、JWT_EXPIRES_DAY 需自行在 .env 設定 res.status(200).json({ status: 'success', user: { token, name: user.name } }); })); ``` --> 回報區 --- <!-- 將答案貼至下方表格內,格式: | Discord 暱稱 | [CodePen](連結) | --> | Discord | CodePen / 答案 | |:-------------:|:-----------------:| | xxx | [CodePen]() | | 2魚 | [CodePen](https://codepen.io/ijrekmsn-the-sans/pen/eYowWKQ) | | 苡安 | [hackmd](https://hackmd.io/@L7K9-66lSeagS28AP0_GjQ/S1yhLky7C) | |Lobinda|[HackMD](https://hackmd.io/@Lobinda/rJ7V91JmC)| | Chia Pin | [CodePen](https://codepen.io/joker-cat/pen/YzbPjdP) | | Aida | [CodePen](https://codepen.io/ada23410/pen/qBGEyLG?editors=0010) | | wei | [CodePen](https://hackmd.io/@xu7yoa5cSsqaron7h9XhUw/H1pd4LJQ0)| | jenny7532 | [CodePen](https://codepen.io/wei-chen-wu/pen/oNRgOyL)| | william威良 | [CodePen](https://codepen.io/snowman12320/pen/oNRXXXW?editors=1010)| | runweiting | [CodePen](https://codepen.io/weiting14/pen/XWwbmMV)| | william_hsu | [CodePen](https://codepen.io/william8815/pen/XWwbNZj)| | ej_chuang | [CodePen](https://codepen.io/EJChuang/pen/MWdwRQP)| | Tiya | [CodePen](https://codepen.io/Tiya_blank/pen/rNbQPbY)| | Hank | [CodePen](https://codepen.io/tw1720/pen/GRaRJJP)| | zaoannihao | [CodePen](https://codepen.io/ckhwdvrx-the-solid/pen/mdYeNXV)| |Mei|[CodePen](https://codepen.io/l_umei/pen/RwmrYam) | | Fabio20 | [CodePen](https://codepen.io/fabio7621/pen/mdYOPxd) |