# 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裡面一起傳給後端驗證~

## 實作
### 後端產生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裡面,上面例子中,如果登入成功後端會回傳這些資料到前端

我的前端使用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了!

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)