KK 的 Web 實驗室(Day20–22)
有時候莫名還是吃了火鍋
24 成為看起來很強的後端:Cookie 常見屬性
這支影片開始,kk 老師的聲音變得比較和緩,而且清晰許多。
現在的影片如果用 1.5 倍速聽就會比較像之前的語速。
而且聲音也變得更深層好聽,滿有趣的變化。
先來複習 Session 和 Cookie 的機制
Client 端 Browser 打一個 /login 給 Sever ,Server 在 Response Header 回傳一個 Set-Cookie: sid = xxx。

Set-Cookie 有兩種做法
一種方式是 Set 已經加密好的資料。
另一種方式是比較常見也是我們這裡使用的,存 session ID 。

Session store 裡面會有一張表,存著我們的 sid (xxx) 和存了什麼字。

Browser 也會自動幫我們把 Server 回傳的 sid = xxx 存起來。

假設這是一個電商網站,想下單,打一個 /orders ,Browser 會在 Request Header 自動帶上 Cookie: sid = xxx。

Server 會去 Session store 做檢查,沒問題之後,就把資料回傳。
複習完畢。
這樣會有什麼問題呢?
第一個問題是「作用域」的問題
什麼情況下, Browser 會自動幫我們帶 sid = xxx 過去?
根據不同的 Server 我們有「第一方 Cookie」跟「第三方 Cookie」模式。
「第一方 Cookie」網域(domain)會是相同的。
舉例來說,Browser 網址的位置是 www.example.com ,Server 的位置是 api.example.com。

如果網域不相同,則稱為「第三方 Cookie」。
例如:天貓或掏寶背後都是阿里巴巴。

另外一個例子就是我們平常會聽到的「跨網域追蹤」,透過在網站內埋碼,利用臉書或Google追蹤、分析使用者行為。
例如:電商網站(Browser)放臉書的追蹤碼(Server),這種也是「第三方 Cookie」。
有些瀏覽器會預設把「跨網域追蹤」選項關掉,網站就無法把這些資料傳給第三方。
但如果網域相同,可能會造成無法登入的情形。
好處是可以防止廣告商分析,壞處可能是大型集團共用 api 就需要針對這個情形做調整。
各種屬性(限定使用區域、限定使用時間)
我們先談談「限定使用區域的屬性」。
Set-Cookie:sid=xxx 後方會有屬性。
第一個是 Domain,可以指定哪一些網站要帶 Cookie 過去。
例如:Domain 設定為 api.example.com ,這樣其他的就不會帶過去。

第二個是 Path ,之前談 HTTP 有提過,就是網域斜線後面的路徑。
例如: 設定為 Path = /login, /order ,只有這些路徑才會把 Cookie 帶過去。

第三個是 Secure ,如果你的 api 沒有 HTTPS 就不會帶 Cookie 過去。

接著談談「限定使用時間的屬性」。
使用 Expires ,就能限制到某一個特定時間點,如: 2020/1/1。
使用 Max-Age ,就能限制固定時間內使用,如: 1 day 。

設定上面這兩種屬性,Browser 內部就會啟動計時器,在適當的時間把 Cookie 刪除。
而這種稱為 Persistent Cookie ,就算關掉分頁、瀏覽器,只要條件沒有符合,Cookie 就會在。

反之稱為 Session Cookie 。(還記得嗎?前面提過 Session 指的是「一段時間內」的「狀態」。)
所以關掉之後,就會消失。
這邊補充比較特殊的「限定使用區域的屬性」。

SameSite 有三種模式: Strict、None、Lax 。
設定 Strict 只允許網域相同的「第一方 Cookie 」。
設定 None 就是不管什麼作用域都會帶 Cookie。
設定 Lax 就要看情形,如果是 GET 的 Request 就不管網域、而 POST 和其他的 Request 網域需要相同。
不過,以上這些都是屬於 Server 端控制的,就算你通通允許,Browser 如果有擋,那也沒有用。
(這段我一直以為窗外有貓…原來戴耳機聽影片這麼真實)
Browser 是怎麼儲存 Cookie 的呢?
使用者、開發者是有可能拿 Cookie 做一些事情的,此時就可以用 HttpOnly 的 flag 。
瀏覽器就會知道,這個 Cookie 禁止被 JavaScript 存取(預設可以拿到 sid),只有在傳輸的時候透過瀏覽器傳輸才能。
25 成為看起來很強的後端:JWT 跟 Session 一起來
目前業界到底是怎麼比較安全的做權限驗證、儲存 Token 呢?
快速複習一下 JWT 和 Session 。

JWT 是透過 Browser 打一個 /login 給 Server ,Browser 的 Response Header 帶 token ,由 Browser 存起來。
重點是,Request 的時候是使用 Authorization: Bearer 後面接一個 Token 。

Cookie (有些人稱為 Session)的做法是 Server 的 Response 會把 Session ID 透過 Set-Cookie:sid=xxx 帶回來。
Browser 會自動存起來,下一次 Request 的時候, Server 會透過 Session Store 來驗證。
Browser 如何儲存 JWT 的 Token ?

普遍來說,有兩種做法:
一種是 Local Storage ,後面的章節 KK 老師會做詳細的介紹,類似本地端的硬碟,Browser 有個小區,會存 token=xxx 。
而 Browser 在 local storage 有儲存,就代表 JavaScript 可以存取。
如果別人安插腳本進來,或是使用者的資安意識薄弱,拿到 Token 之後外洩,這都會造成問題。
所以要如何儲存,卻不被拿到?
另一種做法是之前有提過的 HttpOnly Cookie,因為只有瀏覽器能取得和傳輸使用。

為了說明方便,我們暫時稱為 JWT 2.0 。
一樣從 Browser 打一個 /login 到 Server , Server 的 Response 為 Set-Cookie: token = xxx HttpOnly 。
記得 JWT 是 Stateless 的模式,因此 Server 端並沒有儲存的需要。
Browser 也在裡面存的 token=xxx。

這邊會遇到一個問題,因為 Cookie 是 Browser 自動帶的,而 token 必須在 Header 使用 Authorization: Bearer 的方式。
當 Browser 自動帶 Cookie: token=xxx ,Server 收到之後…看不懂,無法 handle 。
一種解決方法是,有些後端會不管標準模型,直接把這個 token 拿去驗證。
但標準作法,要用 Authorization 這樣的方式,該怎麼做?
26 成為看起來很強的後端:JWT 跟 Session 怎麼選?
一個可能的作法,是不是直接用 JavaScript 把 Header 改成 Authorization?
但,如果要用 JavaScript 去改 Header,那就需要拿到資料。
如果你要拿到資料,但這裡可是 HttpOnly 啊。

看來 JWT 需要靠 Client 端幫助。
當我們「第一次」透過 /get-JWT 把 token=xxx 傳給 Server 的時候,Server 會回傳一個真正的 Token 。
而「第二次」透過 /private 的時候,就可以放 Authorization: Bearer JWT 。

回傳的 JWT 要存在哪呢?
這時候就能透過 In-Memory 的存放,不存放在任何地方,而是留在記憶體裡面。
可以想成,我們用個變數儲存,重新整理就會消失。
這個把 Session/Cookie 跟 JWT 整合的模式,就是這一、兩年常見的模式。
以前面的例子來說,「第一次」帶給 Server 的 token 稱為 Refresh Token 。
(幫我們 JWT 做 refresh 的 token,透過 Refresh Token 的 API 把 token 帶給 Server )

「第二次」拿到的 token 則有些人稱為 Auth Token (較常見)或 Access Token。
所以,我們「第一次」透過 Refresh Token 拿到 Auth Token。
而「第二次」使用的 Auth Token 會記錄所有跟權限有關的資訊,使用 JWT 的格式實作。
那到底為什麼要做這麼麻煩呢?
To be continued…