---
# System prepended metadata

title: Authentication and Authorization
tags: [Blue 的學習紀錄]

---

---
title: Authentication and Authorization
tags: Blue 的學習紀錄
---
# Authentication and Authorization
:::info
**Authentication**：
驗證
檢查身分
確認使用者的身分是否符合使用者所聲稱的身分
**Authorization**：
授權
檢查行為
確認使用者是否擁有權限進行特定操作
:::

如何做登入的功能？
伺服器如何驗證使用者的身分？
伺服器如何驗證使用者的行為？

# state
- Stateful
  儲存 authorization info 在 server side，已有一段歷史的作法
- Stateless
  儲存 authorization info 在 client side，較新的做法，通常會搭配簽署來確保資料沒被修改過

# token
可以將 token 想像成識別證

在 client-server 的架構中，為了讓 server 辨認接收到的 request 身分為何，有各式各樣的方法讓 client 傳送 token 給 server

例如將 token 放在：
- URL，例如 `test.com/?token=xxx`
- HTTP body
- HTTP Header：Cookie
- HTTP Header：Authorization

token 放在 URL 非常不安全，光是在歷史瀏覽紀錄上就暴露了 token

放在 HTTP body 則是不符合規範，例如 GET 時 body 必須為空，不能有任何東西

放在 cookie 則是一個已經相當成熟的做法
瀏覽器對於 cookie 的支援度極高
在歷史的考驗之下，瀏覽器對於 cookie 的保護已經有一定的安全性
但各品牌瀏覽器對於 cookie 的控管可能會有些微差異，這是需要特別留意的部分

在前端頁面以 AJAX 類的方法，將 token 放在 Header 的 Authorization 進行傳遞
token 的傳遞需要工程師於前端進行管控，而不像是 cookie 由瀏覽器控管

# HTTP Basic Authentication
對 web resources 做 access control 的最簡單作法
是 HTTP 協定內建的方法

當瀏覽器收到的 response header 內含有 `WWW-Authenticate: Basic`
瀏覽器就會向 user 發起 challenge，需要輸入使用者名稱與密碼


輸入後的使用者名稱與密碼會以 Base64 的方式編碼後傳送

![](https://mdn.github.io/shared-assets/images/diagrams/http/authentication/basic-auth.svg)
> 圖片來源：https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication

因為是 encode 而不是 hash 或 encrypt
所以在 HTTP 的環境下是不安全的

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

:::info
若 server 的 challenge response 沒有設定 realm 的內容
則在 challenge 上顯示的是 host name
但即使設定了 realm 的內容
在 chrome 仍然只會顯示 host name

https://stackoverflow.com/questions/45666306/change-basic-http-authentication-realm-and-login-dialog-message
:::

優點：
- 簡單方便

參考資料：
- https://en.wikipedia.org/wiki/Basic_access_authentication
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate

# Cookie
cookie 表示為放在 http header 上的一小段資料，通常 max size 為 4096 bytes

例如可以將登入成功後收到的 token 以 cookie 的方式，在接續的 request 都帶上這份 cookie，server 收到 cookie 便能知道這是已經授權過的 user 所發送的 request，便能回傳需要授權的資料

cookie 的作用範圍：
https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Cookies#cookies_%E7%9A%84%E4%BD%9C%E7%94%A8%E7%AF%84%E5%9C%8D

實務上來說，當 server 收到 client 的登入請求，驗證帳號密碼正確後，在 respond header 加上 `Set-Cookie: key=value`
client 收到這樣的 respond 後，瀏覽器就會儲存 `key=value` 的資訊。在之後的 request 就會在 header 中加入 `Cookie: key=value`

假如 cookie 的內容含有 `admin=true/false` 的設定，如何確保 user 不會擅自更改設定？

一個直覺的方式就是對 cookie 的內容做加密，加密雖然可以一定程度阻止 cookie 內容被修改，但是會增加伺服器的運算負擔；且加密後的字串通常長度會增加，cookie 的 max size 只有 4096 bytes，加密會限縮 cookie 可以傳遞的內容大小

替代解決方法是 session store，不再將敏感資訊放在 cookie，而是放在資料庫，cookie 的內容則改為存放號碼牌(session ID)，server 收到號碼牌後去資料庫讀取資料來得知使用者是誰

流程變成：使用者登入成功後，伺服器建立一組 session data，ID 放在 cookie 傳給 user，與 ID 對應的 data 則存放在資料庫
當收到 request 內的 cookie 含有特定 ID 時，查詢資料庫確認後，便認定這是經過登入後 的 request

ID 不能是簡單可預測的數字，例如 `001`、`123` 這類的簡單數字，而應該是一長串難以預測其規則的字串
這樣才能防止惡意使用者猜測他人的 session ID

session store 的缺點在於，使用者每當在需要授權的頁面進行任何操作，伺服器都得再去跟 session store 進行 token 的驗證

所以就算是使用 Redis 這種 in-memory 的快速資料庫，只要使用者足夠龐大，session store 的驗證方式將成為效能瓶頸

參考資料：
- https://youtu.be/NUGMceeFDnU
- https://youtu.be/z0wPnx1bfcQ

## session store 要注意的細節
在這之前，可以先閱讀胡立寫的 [session cookie 介紹文章](https://hulitw.medium.com/session-and-cookie-15e47ed838bc)
裡面以貼近生活的抽象化比喻解釋了一遍 session 和 cookie 到底是什麼；並且再追根究柢的帶領讀者看一遍 RFC 文件上的相關定義；最後以實作來加強講解整個概念

session store 要注意的細節
- session id 必須足夠複雜而難以猜測
- 如何確保 session id 不被竊取？
- 每組 session id 必須獨一無二

## express-session
資料來源：
https://github.com/aszx87410/blog/issues/46
套件：
https://github.com/expressjs/session

express 用於處理 session 的 middleware
範例(取自以上資料來源)：
```javascript=
const express = require("express");
const session = require("express-session");

const app = express();
const port = 8080;

// 使用 session middleware
app.use(
  session({
    secret: "keyboard cat",
  })
);

app.get("/", function (req, res, next) {
  // 可以用 req.session 拿取存在 session 的值
  // 這邊判斷有沒有 req.session.views
  // 如果有的話就 +1，反之初始化成 1
  // 所以 req.session 可讀也可寫
  if (req.session.wow) {
    req.session.wow++;
    res.write("views: " + req.session.wow);
    res.end();
  } else {
    req.session.wow = 1;
    res.end("welcome to the session demo. refresh!");
  }
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}!`);
});
```

觀察範例後可以發現，這個套件做到的是：
- `req.session.wow` 即相當於 `Set-Cookie` 的功能
- cookie 內的 attribute 名稱預設為`connect.sid`，並不是範例裡的`wow`
- `connect.sid` 的內容會是一組已經經過簽署(sha256 + secret)及編碼(base64)的 session ID
- session data 會暫存在 process 裡，所以伺服器重開後，session data 就會消失
- cookie 的生命只存在於瀏覽器的工作階段結束前

所以要應用於 production 時，還得將其 session data 儲存到資料庫

# Json Web Token
將 JSON 資料經過特殊處理後即可變成 JWT
使用者登入成功後，伺服器回傳 JWT 給使用者
使用者在接下來的 request 將 JWT 放在 HTTP Header 的 `Authorization: Bearer JWT`
若伺服器生成的 JWT 有經過簽署，則伺服器就能單獨驗證使用者傳遞的 JWT，不必經過資料庫

JWT 適用於微服務、API，好處在於不需要中心化的資料庫系統來做驗證，單靠伺服器即可與使用者溝通，解決了 cookie 需要頻繁與 session store 做溝通的缺點

但也因為伺服器沒有對 JWT 管理的手段，除了預設 JWT 的過期時間以外，伺服器無法單方面註銷掉特定 JWT
另外，JWT 的內容也不能存放敏感資訊，因為 JWT 的內容(payload)只是經過 base64 的編碼而已，任何人都能輕易存取儲存在 JWT 內的資訊。JWT 能做到的是確保伺服器收到的 JWT 沒被竄改過，並不能阻止 JWT 被第三方讀取

# Signature Cookie？
相較於 session store，也就是 cookie 內傳 session ID 的作法
JWT 的優點在於：
1. 效率，不用 session store 即可單從 server side 進行驗證
2. 可信，JWT 發出去之前已經經過簽署，所以可以驗證傳回來的 JWT 是否經過修改。也就是說，基本上可以相信任何傳回來的 JWT

而 JWT 的缺點(相較於 session store)在於：
1. 實作麻煩，前端要寫 AJAX 來處理 JWT 的儲存以及發送；cookie 則是瀏覽器自動處理，非常方便
2. 安全性，JWT 極容易被讀取；而 cookie 則擁有 HttpOnly 來防止 js 讀取，以及 SameSite 防止 CSRF

既然如此，為何不對 cookie 也做簽署和傳遞 payload(cookie-based session)？
簽署後的 cookie，不但維持原來的安全性，而且也能確保來源可信
放入 payload 後的 cookie，也一樣不需要 session store 進行驗證
也就是簽署的 cookie 看似集了 JWT 和 cookie 的優點於一身？
為何不這麼做？而是普遍還在使用 JWT？

# OAuth 2

<https://developers.google.com/identity/sign-in/web/sign-in>

<https://tools.ietf.org/html/rfc6749>
