---
# System prepended metadata

title: Cookie
tags: [' JavaScript', Cookie]

---

Cookie
===


### 參數
| Domain | Path | Expires / Max-age |
| -------- | -------- | -------- |
| 哪些網域可以存取這個 cookie | 哪些路徑可以存取這個 cookie | 設定 cookie 的有效期限 |


| Secure | HttpOnly | SameSite |
| -------- | -------- | -------- |
| 讓 cookie 只能透過 https 傳遞 | 防止 JavaScript 存取 cookie | 防止 cookie 以跨站方式傳送 |

### SameSite 參數的設定

|| strict | lax | none |
| -------- | -------- | -------- | -------- |
| 跨域 | X | X | O |
| gmaill | X | O | O |



介紹
---
Cookie 是儲存在瀏覽器的小段文字資料，通常由伺服器透過 `Set-Cookie` header 傳遞給瀏覽器。瀏覽器收到後會將 cookie 儲存起來，並在之後的請求回傳 cookie 至同樣的伺服器。

### 用途
Cookie 常見用途是認證身份，例如登入狀態、購物車等，也被應用於追蹤使用者及廣告上。

Cookie 也被用於客戶端的儲存方式，但由於 cookie 會被附加在每一次的 request 之中，可能會影響效能，所以如果是不需要記錄在 server 的資訊，可以改用 storage API。

### Set-Cookie header
伺服器（Server）可以在 HTTP response 中回傳 `Set-Cookie` header 告訴瀏覽器要設定 cookie。
```
Set-Cookie: [cookie名稱]=[cookie值]
```
瀏覽器看到 `Set-Cookie` header 便會將 cookie 儲存起來，之後對同一個域名（domain）發送 HTTP request 的時候，瀏覽器就會將 cookie 帶在 HTTP request 的 Cookie header 裡。

Request 中的 cookie header 會是 `[cookie名稱]=[cookie值]` 的形式。
```
Cookie: [cookie1]=[value1]; [cookie2]=[value2]
```

[使用](https://codepen.io/betty-hu/pen/qBVLYzV?editors=1010)
---

### 使用 JavaScript 讀取 Cookie
在 JavaScript 中，可以使用 `document.cookie` 讀取 cookie 。
```js
console.log(document.cookie);
```

讀取出來的 `document.cookie` 會是字串，是將網域底下所有 cookie 用分號串接的結果，其中每個 cookie 都是 `[cookie名稱]=[cookie值]` 的形式。

例如：`name=John; gender=male` 表示這個網域底下有兩個 cookie：`name` 和 `gender`，其中 `name` 的值是 `John`，而 `gender` 的值是 `male`。

如何讀取特定 cookie 的值，例如：如何從 `name=John; gender=male` 的字串得到 `name` 和 `gender` 這兩個 cookie 的值？試著自己寫一個函式 parse，或是利用較為成熟的第三方套件，如：cookie等。
```jsx
// Reference: https://stackoverflow.com/questions/10730362/get-cookie-by-name
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}
```
```jsx
getCookie('name')
```

### 使用 JavaScrip 寫入 Cookie
使用 JavaScript 寫入 cookie 的方式是 `document.cookie = 'key=value;'`。

雖然以下使用 document.cookie = ...，但是並不會整個 cookie 都被覆蓋，只有指定的 key 會被更新。

如下面例子，cookie3 被新增的同時，原本的 cookie1 和 cookie2 還會保留。

```jsx
document.cookie = 'cookie=value';
```

Cookie 的參數
---
Cookie 除了名稱和值以外，通常需要設定其他額外參數。

新增參數的方式，用分號區隔各個參數，例如：
```
user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT
```
使用 `domain` 和 `Path` 指定 cookie 的可用範圍，使用 `Expires` 和 `Max-Age` 控制 cookie 的有效期限，而 `HttpOnly`、`Secure`、和 `SameSite` 則是安全性相關參數。

### Domain
```
domain=example.com
```
`domain` 指定哪些網域可以存取這個 cookie。

預設值是當前網域，但是**不包含其子網域**。

例如在 `example.com` 底下設置的 cookie 不指定 `domain` 的情況，只有 `example.com` 可以存取此 cookie，但子網域如 `subdomain.example.com` 則無法存取此 cookie。

如果想要讓子網域存取 cookie，必須明確設定 `domain` 參數。

例如：當一個 cookie 指定 `domain=example.com` 時，包含 `example.com` 以及他的子網域 `subdomain.example.com` 都能夠存取這個 cookie。

### Path
```
path=/admin
```
`path` 指定哪些路徑可以存取這個 cookie。

例如：假設 domain 是 `example.com`，且 path=/admin，則 `example.com/admin` 或是子路徑 `example.com/admin/settings` 都可以存取此 cookie，但 `example.com` 或是 `example.com/home` 則無法存取此 cookie。

Path 的預設值是當前的路徑。

一般而言，認證用途的 cookie 會設成 `path=/`，讓全站可以存取此 cookie，如此一來不管使用者在網站的哪個路徑，伺服器（Server）都能認出使用者的身份。


範例：[全域音樂](https://codepen.io/betty-hu/pen/VYeyMRw)
```js
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

const audio = document.querySelector("audio");
// 離開網頁
window.addEventListener('visibilitychange', function () {
    if (document.visibilityState === 'hidden') { 
        document.cookie = `key=${audio.currentTime};path=/;` // 寫入 cookie
                                                 // path=/; 作用範圍為全域
    }
});
// 載入網頁
window.addEventListener('load', function () {
    audio.currentTime = getCookie("key"); // 讀取 cookie
});
```



### Expires, Max-age
`expires`, `max-age` 設定 cookie 的有效期限。

如果沒有額外設定 `expires` 或是 `max-age` 參數，當瀏覽器關閉之後，儲存在瀏覽器的 cookie 便會消失，這就是所謂的 **session cookie**。

如果希望瀏覽器關掉之後 cookie 還是會被儲存下來，就必須設定 `expires` 或是 `max-age`。

`expires` 是 UTC 格式表示的有效期限，在 JavaScript 中可用 `date.toUTCString()` 取得：
```
cookie=value; expires=Tue, 19 Jan 2038 03:14:07 GMT
```
`max-age` 表示從設定開始算之後幾秒之內 cookie 是有效的：
```
cookie=value; max-age=3600
```

### Secure
`Secure` 設定 cookie 只能透過 https 傳遞。

Cookie 預設不區分 http 或是 https。當設定 `http://example.com` 的 cookie 時，`https://example.com` 也能看到同樣的 cookie。

如果 cookie 設了 `secure` 參數，只有透過 https 存取這個網站才能存取這個 cookie；透過 http 存取這個網站就看不到這個 cookie。

這個參數的作用在於保護 cookie 只能在 https 傳遞。話雖如此，仍是不能將敏感資訊儲存在 cookie 中。

### HttpOnly
`HttpOnly` 防止 JavaScript 存取 cookie。

當 cookie 設置 `httpOnly` 屬性，JavaScript 就不能存取這個 cookie，但是瀏覽器在發送 request 時，還是會幫你帶在 request header 裡面。

這是為了安全性考量，如果 JavaScript 能夠存取這個 cookie 就有受到 XSS Attack (Cross-Site Scripting，跨站腳本攻擊) 的風險。

#### XSS Attack (跨站腳本攻擊)
將惡意的 JavaScript 程式碼透過表單方式上傳到 server，這份表單資料在前端呈現時，惡意的 JavaScript 程式碼會被當成是 HTML 的一部分被執行。

假設攻擊者能夠執行 JavaScript，便能輕易地存取 `document.cookie`，就能夠竊取受害者登入的 cookie，並且以受害者的身份做惡意的操作。

### SameSite
`Samesite` 防止 cookie 以跨站方式傳送，避免 CSRF (Cross-Site Request Forgery，跨站請求偽造) 攻擊。

#### CSRF (Cross-Site Request Forgery，跨站請求偽造) 攻擊
在受害者已登入的狀態，假借受害者的身份進行惡意操作。

例如：將受害者銀行裡的錢轉到攻擊者的帳戶。

#### 具體如何進行 CSRF 攻擊？

假設含有使用者的機敏資訊網站叫做 `bank.com`，使用者成功登入 `bank.com` 後，會收到加密過代表使用者身份的 cookie，後續請求（request）都會帶有這個 cookie。

因為 cookie 經過加密無法輕易偽造，所以伺服器（server）認證請求（request）來自使用者本人，也會同意轉帳等敏感操作。

攻擊者透過釣魚信件方式讓受害者不小心進入攻擊者的網站 `evil.com`，其內包含一小段發送表單的代碼，會把 `bank.com` 的錢轉到攻擊者的戶頭。

因為送往 `bank.com` 的 request 帶有表示身份的 cookie，包含發送表單的 POST request，所以 server 認為這個 request 沒有問題，錢就會轉給攻擊者。

> 通常會在表單中多帶伺服器（server）產生的 CSRF token 防範此攻擊，與 `SameSite` 兩防護措施同時並行。

> 在 `evil.com` 發送 `bank.com` 請求的情境下，`back.com` 的 cookie 就是所謂的第三方 cookie。

#### 跨域請求
當請求的網域和網址列中的網域不同時，它就是一個跨域請求。在範例中，瀏覽器的網址列的網域是 `evil.com`，但是送往 `bank.com` 網域的 POST request，因此這個 POST 是跨域請求。

跨域請求攜帶 cookie 就有遭受 CSRF 攻擊的風險。

#### `SameSite` 參數如何防範 CSRF 攻擊

`SameSite` 共有三種不同設定，分別對應不同的安全性層級：

- `SameSite = strict`

    `Strict` 表示 request 的網域必須跟網址列中的網域相同，才會發送這個 cookie。

    以範例而言，發送轉帳的 POST 請求時，因為屬於跨域請求的關係，並不會攜帶表示身份的 cookie，就不會受到 CSRF 攻擊。

    如果是從 email 導過去 `bank.com`，`SameSite` cookie 也不會被發送給 server。這可以保護使用者不會因為點了釣魚信件的連結就轉帳給攻擊者。

    缺點是，即使信件確實來自 `bank.com`，也一律需要使用者重新登入，相對而言較為不方便。所以這個設置比較適合用於敏感操作，例如轉帳、修改密碼等。
    
- `SameSite = lax`

    `Lax` cookie 和 `strict` 相比限制較為寬鬆。瀏覽器發送跨網域的 request 一樣不會攜帶 `lax` cookie，除了導向目標網址的 GET request。

    但是，單純地在瀏覽器開啟連結，或是從 email 點開連結就會攜帶 `lax` cookie。

    注意：[從Chrome 76 開始，預設值為 SameSite=lax](https://chromestatus.com/feature/5088147346030592)。意味第三方 cookie 在沒有明確設定 `SameSite` 的情況下會失效。
    
- `SameSite = none`

    跨域的情況下仍會送出 cookie。

    注意：[從Chrome 80 開始，使用這個選項必須同時開啟 Secure 參數](https://chromestatus.com/feature/5633521622188032)。

    如果你的產品仰賴第三方 cookie，例如廣告、iframe 嵌入套件等，應該要使用這個選項。

第三方 Cookie（Third-Party Cookie）
---
網頁需要向其他網域請求資源，例如：使用 `<img src="...">` 的方式嵌入一張其他網域的圖片。這些 request 可以攜帶 cookie，攜帶哪些 cookie 主要根據資源的網域。


#### 第一方 cookie (first-party cookie)
使用者瀏覽 `example.com`，其中包含圖片 `<img src="https://example.com/image.png">`，此時攜帶的 cookie 會是 `example.com` 底下的 cookie。

因為請求的網域和網址列的網域同樣是 `example.com`，這是相同網域的請求。

此時 `example.com` 底下的 cookie 又稱作第一方 cookie (first-party cookie)。


#### 第三方 cookie (third-party cookie)
如果 `example.com` 包含另一張圖片 `<img src="https://ad.com/image.png">`，網域是 `ad.com`，此時攜帶的 cookie 就會是 `ad.com` 底下的 cookie。

因為 `ad.com` 不同於網址列的 `example.com`，所以這是跨域請求。

此時 `ad.com` 底下的 cookie 又稱作第三方 cookie (third-party cookie)。

#### 第三方 cookie 為何重要？
第三方 cookie 能夠跨網域追蹤。

> 例如：
> 
> `example.com` 發出 `ad.com` 請求時，會攜帶 `ad.com` 的 cookie。
> 
> 如果同時另一個網域 `anothersite.com` 也來請求 `ad.com` 的資源，也會攜帶同樣的 cookie。
> 
> 如果這個 cookie 用來表示使用者 id，則對 `ad.com` 而言，都會知道兩個網站的造訪者。
> 
> 這是廣告追蹤的原理。

這就是為何對第三方 cookie 的限制增多：在 2020 年，在 Chrome 必須要明確標示 `SameSite=None; Secure`，否則預設情況 `SameSite=Lax`，第三方 cookie 不會被發送的。而 Safari 完全禁止第三方 cookie。

Session 與 Cookie差別
---

http 本身是無狀態（Stateless）協議，可以在 Client 與 Server 兩端進行溝通，但是無法紀錄網路上的行為。

如果登入一個網站，每次訪問該網站時，需要將登入帳密再輸入一次，或是現在頁面上的資料填到一半，不小心把網頁關掉，重開頁面只好再重新輸入一次，使用起來非常不便。

Cookie 和 Session 因此誕生，解決無紀錄狀態的問題。

### Cookie

由 Server 送給瀏覽器的文檔資料。瀏覽器儲存，並在瀏覽器下一次發送要求時，將它送回原本送來的 Server。

實例說明：網站可以透過 Cookie 的方式，紀錄之前輸入的帳號密碼，省去每次重打帳密的不便，只要 Cookie 尚未到期，瀏覽器會傳送該 Cookie 給伺服器作驗證憑據。

#### 特性
- 特定網域：只針對原本的網域（domain）起作用。
- 生命期限: 到了設定的生命期限後失效。
- 在向該網域的 server 發送請求時，也會一併帶進該請求。

#### 缺點：
- cookie 的所有數據在 Client 端可以被修改，重要數據不能存放在 cookie 中。
- 如果 cookie 數據字段太多會影響傳輸效率。

為了解決這些問題，產生 session。session 的數據保留在 Server 端。

### Session

Session 紀錄在 server 端的使用者訊息，在用戶完成身分認證後，存下所需的用戶資料，產生一組對應的 ID 存入 cookie 後傳回用戶端。

詳細資料
---
- [什麼是 Cookie？如何用 JS 讀取/修改 document.cookie?](https://shubo.io/cookies/)
- [[XSS 1] 從攻擊自己網站學 XSS (Cross-Site Scripting)](https://medium.com/hannah-lin/%E5%BE%9E%E6%94%BB%E6%93%8A%E8%87%AA%E5%B7%B1%E7%B6%B2%E7%AB%99%E5%AD%B8-xss-cross-site-scripting-%E5%8E%9F%E7%90%86%E7%AF%87-fec3d1864e42)
- [解釋 Cookie 的特性](https://blog.miniasp.com/post/2008/02/22/Explain-HTTP-Cookie-in-Detail)
- [Cookies - SameSite Attribute](https://ithelp.ithome.com.tw/articles/10251288)
- [CORS 是什麼? 如何設定 CORS?](https://shubo.io/what-is-cors/)