---
tags: backend
---
# 前端Cookie
By : 鄭弘詣
---
## Cookie 是什麼?
當你瀏覽某個網站時...
* Server端 : 在Client端電腦系統(硬體、記憶體)中留下一些加密小檔案(文字檔)。
當你再次瀏覽該網站...
* 系統便會去讀取這些小檔案來得知這兩個http request 是來自於同一個User。
* EX : 購物網站(淘寶、蝦皮)的登入、購物車、結帳資訊...等。
---
## Cookie 是什麼?
**Cookie != HTTP協定內容的功能 !!!**
**準確來講,Cookie是一種可以跟HTTP一起使用的功能,並且被廣泛使用。**
複習HTTP : HTTP協定是屬於無狀態(Stateless)的協議,每一次發送請求到結束該連接後都不會記住任何使用者資訊。
EX: HTTP就像是記憶力 = 0 的早餐店老闆娘,永遠不會記得你是誰、點了什麼。
#### 為什麼需要Cookie?
舉例來說,如果每天買早餐的時候,老闆娘都會給你一張點餐明細,之後只要透過這張明細就能知道你點了什麼來給你相對應的餐點,是不是方便許多 ?
==Cookie = 網站上的號碼牌==
---
## Cookie 屬性
Cookie(複數形態為Cookies)是屬於一種小型的文字檔案,透過加密的方式儲存在用戶端(Client Side)上的資料。
1. Name-Value : Cookie的名稱與值。
2. expires : Cookie的保存期限。
3. path : 指定與cookie關連在一起的網頁。默認情況下是"path=/;"。
4. domain : cookie有效的網域名稱,讓相同/類似的domain可以享有同樣的cookie。默認情況下是本機。
5. secure : cookie的安全值,默認情況下是cookie是不安全的。
6. Size : Cookie資料的大小。
取得cookie資訊方法 :
==JS也是透過這document去操作==
```
javascript : document.cookie
//這樣就能知道你的cookie
```
Chrome : 開發人員工具(F12)->Application->Storage->Cookies
---
## Cookie 的缺點
**安全性:** 由於cookie在HTTP中是明文傳遞的,其中包含的數據都可以被他人訪問,可能會被篡改、盜用。
==->因此cookie通常不會放些敏感資訊!==
**大小限制:** cookie的大小限制在4KB左右,若要做大量存儲顯然不是理想的選擇。
==->太大的話就要去做session機制==
如會員機制,還要把其他也常用的資訊放在cookie裡,會放不下,所以要做session的機制,這樣才不會影響到網頁的流量。
(超過的話就要放到server端的session)
**增加流量:** cookie每次請求都會被自動添加到Request Header中,無形中增加了流量。cookie信息越大,對服務器請求的時間也越長。
---
:::info
補充:圖片的快取放在你瀏覽器本身的快取機制,並會存在你的電腦裡面,跟cookie是分開的。
:::
---
## 程式
[**需下載VScode外掛套件 - Live Server**](https://github.com/SeanZhenggg/Cookie)
* name一樣,value不一樣的話,是會去覆蓋上一筆資料
* cookie是以文件形式存在本機端或瀏覽器,看前端怎麼寫
* cookie裡面只會有name跟value,沒辦法找到其他的cookie屬性
cookie-based的session 是以session機制建立並存取大量資料,存在session ID裡,之後造訪網站時,用這id去叫資料。
---
### 參考連結
[ 不是工程師 ] Cookie 是文檔還是餅乾?簡述HTTP網頁紀錄會員資訊的一大功臣。
https://progressbar.tw/posts/91
聊一聊 cookie
https://segmentfault.com/a/1190000004556040
[ 不是工程師 ] 會員系統用Session還是Cookie? 你知道其實他們常常混在一起嗎?
https://progressbar.tw/posts/92
---
# 在React上使用cookie
React-cookie(cookie的套件)
1. npm install
2. method
* set 建立
* get 取得
* remove 移除
----
## Class-based
包起來後裡面就會有cookie的props
```
export default withCookies(App);
```
類似redux的mapStateToProps,使用後App component會多一個叫做cookies的props

透過set 與 get簡單操作
### Functional component
最近很火的react hook,讓functional component可以做到更多事情,建議可以去研究一下。
==主要就是能讓function也使用state==
```
import { useCookies } from 'react-cookie';
```

---
### 實作時間!

一個簡易的表單,input在輸入的同時會即時顯示你在輸入的內容(用state,也就是基本受控表單的概念)

點選add member送出後,將input值存入cookie,並且清空input欄位,並在畫面中呈現出來 hello,{去抓cookie的值} 來歡迎某人的加入。
怎麼確定自己cookie有抓到? 關掉這個頁面的tab,再重開一個新的tab,重新進入網頁,你應該還是要看到 hello {某個人} 的字串,因為瀏覽器已經存取你的cookie了!
#### Class-based
```javascript=
import React, { Component } from 'react';
import { withCookies, Cookies } from 'react-cookie';
class Member extends Component {
state = {
name: this.props.cookies.get('name') || 'oldmo',
inputValue: ""
}
handleSubmit = (e) => {
e.preventDefault();
this.props.cookies.set('name', this.state.inputValue, { path: '/' });
this.setState({
name: this.state.inputValue,
inputValue: ""
})
}
handleChange = (e) => {
this.setState({
inputValue: e.target.value
})
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.inputValue} onChange={this.handleChange}/>
<input type="submit" value="Add member" />
</form>
{this.state.name && <h1>Hello {this.state.name}</h1>}
{this.state.inputValue&& <h1>this is name state: {this.state.inputValue}</h1>}
</div>
);
}
}
export default withCookies(Member);
```
#### Functional component
```javascript=
import React, {useState} from 'react';
import { useCookies } from 'react-cookie';
function Cookie() {
const [cookies, setCookies] = useCookies(["name"]);
const [name, setName] = useState("");
const handleChange = (e) => {
setName(e.target.value); //輸入框裡面的值
//將input裡的值連上要渲染出的東西
}
const handleSubmit = (e) => {
e.preventDefault();
setCookies('name',name, { path: '/' });
//將舊cookie的'name'被input的新name所取代
//path: 這個cookie在哪些路徑會被存取, '/'就是根目錄全部都存取的意思
setName("");
//表單送出時執行的function
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
name={cookies.name}
type="text"
value={name}
onChange={handleChange} />
<input
type="submit"
value="Add member" />
{cookies.name && <h1>Hello {cookies.name}!</h1>} //渲染出cookies存取的name
{name && <h1> this is name state: {name} !</h1>}
</form>
</div>
);
}
export default Cookie;
```
---
# 後端cookie
# 用express實作cookie(袁浩)
## 安裝
```shell
npm install express-session
npm install body-parser
```
## 使用方法
#### 因為要從req.body取裡面的資料
```javascript=1
app.use(bodyParser.urlencoded({
extended: true
}))
```
#### 使用session
```javascript
//設定session基本資料
const session = require('express-session');
app.use(session({
name: 'fespsession',
resave: false,
saveUninitialized:false,
secret: 'testsecret', //session會產生給cookie,用來確認連線並允許cookie存取
cookie:{
maxAge: 1000* 60 * 60 *2,//2 小時
sameSite: true,
secure: true
}
}))
```
### 直接在session中建立變數userID
這個 `= user.id`的user是我自定義的物件
```javascript
req.session.userID = user.id;
//使用者登出時,消滅session
req.session.destroy(callback);
//Cookie 的 Secure 屬性是強迫 Cookie 在傳輸時使用 SSL 加密機制。
//Cookie 大小限制4KB 超過的資訊應放在session。
```
## 參考方法
這邊放一個自動執行的middlware
若找到session有不是null的userID
將這個userID拿去搜尋出對應的user物件
將此user物件放到==共用物件locals==裡面
```javascript
app.use((req,res,next)=>{
const {userID} = req.session;
if(userID){
res.locals.user = users.find(user => user.id ===userID);
}
next();
})
```
:::success
# res.locals
可以直接res.locals.變數A
這個變數A可以被所有middleware存取

:::
### Full code
```javascript=1
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const TWO_HOURS = 1000* 60 * 60 *2
const {
PORT = 3000,
NODE_ENV = 'development',
SESS_LIFETIME = TWO_HOURS,
SESS_NAME = 'sid',
SESS_SECRET = 'ssh!quiet,it/is secret'
} = process.env;
const IN_PROD = NODE_ENV === 'production';
//user 的東西可以放在其他地方,如redis,這邊方便才先寫在這
const users=[
{id:1, name:'Alex',email:'alex@gmail.com',password:'test'},
{id:2, name:'Bach',email:'bach@gmail.com',password:'bachismusic'},
{id:3, name:'Charlie',email:'charlie@gmail.com',password:'thisischalie'},
{id:4, name:'Dima',email:'dima@gmail.com',password:'privet'},
]
const app = express();
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized:false,
secret: SESS_SECRET, //key to sign the cookie
//secret 會幫你把cookiehash成亂數,驗證你是不是同一個user
cookie:{
maxAge:SESS_LIFETIME, //使用期限
sameSite: true, //要不要求同一個domain
secure: IN_PROD //要不要透過ssh安全連線
}
}))
const redirectLogin = (req,res,next)=>{
if(!req.session.userID){
res.redirect('/login')
}else{
next();
}
}
const redirectHome = (req,res,next)=>{
if(req.session.userID){
console.log('Connected Session: ' + req.session.name)
res.redirect('/home')
}else{
next();
}
}
//這是一個middleWare 知道有沒有人正在連,去找相應的id
app.use((req,res,next)=>{
const {userID} = req.session;
if(userID){
res.locals.user = users.find(user => user.id ===userID); //res.locals->express本來就有的東西
}
next();
})
app.get('/',(req,res)=>{
const userID = req.session.userID;
res.send(`
<h1>Welcome!</h1>
${userID? `
<a href='/home'>Home</a>
<form method='post' type='submit' action='/logout'>
<button>Logout</button>
</form>`
:
`
<a href='/login'>Login</a>
<a href='/register'>Register</a>`
}
`);
})
app.get('/home',t,(req,res)=>{
const {user} = res.locals
res.send(`
<h1>Home</h1>
<a href='/'>Main</a>
<ul>
<li>Name:${user.name}</li>
<li>Email:${user.email}</li>
</ul>
`); // A ? () : () -> 三元判斷式
})
app.get('/profile',(req,res)=>{
const {user} = res.locals;
}
)
app.get('/login',redirectHome,(req,res)=>{
res.send(`
<h1>Login</h1>
<form method='post' action='/login' />
<input type='email' name='email' placeholder='Email' required/>
<input type='password' name='password' placeholder='Password' required/>
<input type='submit' />
</form>
<a href='/register'>Register</a>
`)
});
app.get('/register',redirectHome,(req,res)=>{
res.send(`
<h1>Register</h1>
<form method='post' action='/register' >
<input name='name' placeholder='Name' required/>
<input type='email' name='email' placeholder='Email' required/>
<input type='password' name='password' placeholder='Password' required/>
<input type='submit' />
</form>
<a href='/login'>Login</a>
`)
})
app.post('/login',redirectHome,(req,res)=>{//22:33
const { email,password } =req.body;//Because we have bodyparser, we can access req object
if(email && password){
const user = users.find(user => user.email === email && user.password ===password)// TODO:HASH
if (user){
//create cookie
req.session.userID=user.id;
return res.redirect('/home');
}
}
res.redirect('/login');
})
app.post('/register',redirectHome,(req,res)=>{
const { name,email,password } =req.body;
if(name&&email&&password)//TODO:HASH
{
const exists = users.some(
user => user.email === email
)
if(!exists){
const user = {
id: users.length + 1,
name ,
email,
password
}
users.push(user);
req.session.userID = user.id;
return res.redirect('/home');
}
}
res.redirect('/register') //TODO:query /register?error=error.auth.email.TooShort
})
app.post('/logout',redirectLogin,(req,res)=>{
req.session.destroy(err=>{
if(err){
return res.redirect('/home');
}
res.clearCookie(SESS_NAME);
res.redirect('/login');
})
})
app.listen(PORT, ()=>{
console.log(`Listening on http://localhost:${PORT}/`);
});
```