---
# System prepended metadata

title: 前端Cookie
tags: [backend]

---

---
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

![](https://i.imgur.com/joqOPjc.png)

透過set 與 get簡單操作


### Functional component

最近很火的react hook，讓functional component可以做到更多事情，建議可以去研究一下。
==主要就是能讓function也使用state==
```
import { useCookies } from 'react-cookie';
```

![](https://i.imgur.com/DLahXxN.png)

---

### 實作時間!

![](https://i.imgur.com/S8o4mFp.png)

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


![](https://i.imgur.com/UbxCN1v.png)
點選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存取
![](https://i.imgur.com/fNYcGLc.png)
:::

### 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}/`);
});
```


