# JWT(下)- 實作認證 React, Node.js ## 前言 上一篇介紹了[JWT的概念和Session的差別](https://hackmd.io/@emmmmmma/ByD9Jd2ga),本篇要教大家如何從react和node.js的前後端實作一個登入系統的jwt token,如果你是用不同的程式語言開發,其實實作的邏輯概念都一樣,學會了要套用到各種不同的程式語言都是非常簡單的! ## JWT 複習一下,JWT是在使用者登入的時候,後端先確認帳號密碼無誤,接著把相關資料(如id)放在jwt裡面再回傳到前端,並儲存在LocalStorage裡面。 後續有任何的api request前端都會把jwt放在header裡面一起傳給後端驗證~ ![](https://hackmd.io/_uploads/S1_8LAEZ6.png) ## 實作 ### 後端產生jwt 1. 先安裝jwt套件 ```npm= npm i jsonwebtoken ``` 2. 在登入的controller中加入jwt ```javascript= //宣告資料表 const Users = require('../../../domain/model/user') //宣告jwt token const jwt = require('jsonwebtoken'); const email = req.body.email; const password = req.body.password; // 後端使用sequlize的orm 可以把它想像成是sql語法中的select * from User where User.email = email; Users.findOne({where:{ email:email }}) .then(data => { if(!data){ // 資料庫沒查到該email res.status(200).send({status : false, message:'查無信箱'}); }else if(password === data.password)){ // 登入成功,產生 JWT const payload = { user_id: data.id, user_email: data.email, user_name: data.username, }; // 設定jwt 時間限制為1小時 const token = jwt.sign({ payload, exp: Math.floor(Date.now() / 1000) + (60 * 60) }, 'secret key'); // 回傳登入成功和jwt token res.status(200).send({status : true, message:'登入成功', token:token}); }else{ res.status(200).send({status : false, message:'密碼錯誤'}); } }) .catch(err => { console.log(err) res.status(500).send({message:err.message}); }); ``` :::info :warning: 因為jwt的secret key是不能被別人知道的,直接寫在前後端程式碼裡面會有資安的疑慮,所以通常會建立一個.env的檔案並且把密鑰寫在裡面! > JWT_TOKEN = secret key 並在要使用的程式中安裝npm i dotenv require('dotenv').config(); 並在上方24行中用const token = jwt.sign({ payload, exp: Math.floor(Date.now() / 1000) + (60 * 60) }, **process.env.JWT_TOKEN**); 來取代 ::: ### 前端接收 1. 登入成功後將jwt存到localstorage裡面,上面例子中,如果登入成功後端會回傳這些資料到前端 ![](https://hackmd.io/_uploads/r1zBgJSW6.png) 我的前端使用react和axios的方式呼叫api,下方為使用者輸入電子信箱和密碼後的登入函式 ```javascript= const login = async() =>{ axios.post('http://localhost:8000/api/user/login',{ email:email, password:password}) .then(data =>{ if(data.data.status){ alert('Login Success !') // 把後端回傳的token存在localStorage裡面 localStorage.setItem('token', data.data.token) // 導回主頁面 window.location.assign('/'); }else{ alert(`Login Fail : ${data.data.message}`) } }).catch(err =>console.log(err)) } ``` 此時從F12的Application中,就可以看到存好的jwt了! ![](https://hackmd.io/_uploads/Bycgbyrb6.png) 2. 呼叫其他api時,把jwt 放到headers中即可 ```javascript= const token = localStorage.getItem('token') axios.post('http://localhost:8000/api/other', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', } }) .then(data => { console.log(data) }).catch(err => { console.log(err) }) ``` :::info :writing_hand: 如果把jwt放在一般body的欄位axios.post('http://localhost:8000/api/other', {token:token})當然也可以有一樣的效果,但是**安全性**上會有差,本篇不多贅述兩者差別,先記得放在headers的Authorization的安全性是比較高的這樣就好XD ::: ### 後端解析 1. 安裝套件 npm i jsonwebtoken dotenv 2. 解析jwt ```javascript= const jwt = require('jsonwebtoken') require('dotenv').config(); // 取得前端傳來的jwt const token = req.body.headers.Authorization; if (!token) { console.log('no token') return res.status(401).json({ message: 'no token' }); } else { jwt.verify(token.split(' ')[1], process.env.JWT_TOKEN, async (err, decoded) => { if (err) { console.log('token err :', err); return res.status(401).json({ message: 'token error', detail: err }); } else { // 後端邏輯 return res.status(200).json({data:data}) } }) } ``` 透過上述的jwt驗證不但可以看有沒有過期,也可以看出內容有沒有被改動,只要有錯誤都會進到上面12,13行的地方,並且觀察console會發現 : - token err : TokenExpiredError: jwt expired (過期) - token err : JsonWebTokenError: invalid signature(被竄改過) ## 總結 大家在開發前端時,一定會遇到登入系統的問題,怎麼確保資料不被盜取,還有使用者體驗的部分,JWT真的是一個方便且容易上手的工具,學會之後就可以更加精進整體能力~ ## 參考 [圖片來源 - [筆記] 透過 JWT 實作驗證機制](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98-%E9%80%8F%E9%81%8E-jwt-%E5%AF%A6%E4%BD%9C%E9%A9%97%E8%AD%89%E6%A9%9F%E5%88%B6-2e64d72594f8)