iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
6
Modern Web

你所不知道的各種前端 Debug 技巧系列 第 26

[Day 26] Cookies - SameSite Attribute

Chrome 從 84 版開始將 Cookie 的 SameSite 屬性預設為 Lax,使用到 Third-party cookies 的服務若沒有設定 SameSite 都可能受到影響。

概覽

Cookies 是網頁服務中用來儲存狀態的機制,常被用在保持登入、購物車、廣告追蹤等等,但在 Cookies 的廣泛使用下,同時也伴隨著隱私和安全的疑慮,而 SameSite 的出現就是為了解決這些問題。

 

First-Party and Third-Party

依據 Cookie 的來源(Set-Cookie),每個 Cookie 都有專屬的 Domain,以使用者瀏覽器當下的網址來看,只要 Cookie 的 Domain 和目前的網址相符就是 First-Party,反之就是 Third-Party。

例如瀏覽 a.com 網站時發送 Request 到 third-party.com 並拿到了 third-party.com 的 Cookie,由於瀏覽器會在 Request 時自動帶上相同 Domain 的 Cookie,之後瀏覽了其他網站如 b.com 時若也發送 Request 到 third-party.com,Server 就會收到 Cookie,對這兩個網站來說 third-party.com 的 Cookie 就是 Third-party。

如果瀏覽符合 third-party.com Domain 的網站也會帶上 Cookie,此時這個 Cookie 就稱為 First-party。

 

Same-Origin and Same-Site

剛才的例子提到以 Domain 是否符合來判定 Cookie 的種類,不過更好的說法應該是以 Site 是否相同來判定,而這和常常看到的 Same-origin 是否有關呢?

Origin

Origin 是由 Scheme, Host, Port 組成,判定方式非常簡單,只要兩個網址的 Scheme、Host 和 Port 都相同就是 Same-origin,其餘皆是 Cross-origin。

Site

Same-Site 的判定則牽涉到 Effetive top-level domains(eTLDs),所有的 eTLDs 被定義在 Public Suffix List 中,而 Site 是由 eTLD 加上一個前綴組成。

舉例來說:
github.io 存在 Public Suffix List 之中,加上一個前綴(例如 a.github.io) 就是一個 Site,因此 a.github.iob.github.io 是兩個不同的 Site(Cross-site)。

example.com 不存在 Public Suffix List 之中,但 .com 存在,因此 example.com 是一個 Site,a.example.comb.example.com 就是同一個 Site(Same-site)。

注意 Site 不包含 Port,即使 Port 不同也可以是 Same-site

 

Why SameSite?

「任何 Request 都帶上該 Domain 的 Cookie」的機制同時也帶來了安全和其他問題,其中最重要的就是 Cross-site request forgery(CSRF)。

CSRF

假設使用者曾經登入過 example.com 並取得 Cookie,當使用者瀏覽惡意網站 evil.com 時,網站中的 JavaScript 可以對 example.com/pay?amount=1000 發出 POST Request,瀏覽器會自動帶上 example.com 的 Cookie,使用者就在完全不知情的狀況下付了 1000 元,Server 無法判定這個 Request 是從何而來。

限制

Cookie 本身無法被設定為只在 First-party 環境才發送,因此 Request 在任何環境都會帶上 Cookie,Server 無法辨識 Request 來源只能照常回覆,同時也讓 Client 浪費流量送出無用的 Cookie,

解決

有了 SameSite 屬性後,就可以個別設定 Cookie 在不同環境下的發送條件。
 

SameSite

SameSite 屬性共有三種值,設定為 StrictLax 可以限制 Cookie 只在 Same-Site Request 帶上,若不填則依據瀏覽器可能有不同行為,以 Chrome 來說預設值為 Lax

Strict

只在 First-party 環境下帶上 Cookie,但這有個問題,假設使用者在 example.com 看到一條 FB 貼文連結(假設為 fb.com),就算使用者曾經登入過 fb.com 取得了 Cookie,點擊連結後因為兩個網站為 Cross-site,不會帶上 Cookie,只能看到登入頁面。

因此 Strict 適合用在操作,例如刪除貼文、付款等等。

Lax

為了解決 Strict 過於嚴格的限制,Lax 在以下情況即使是 Cross-site 依然會送出 Cookie:

  • 在網址列輸入輸入網址
  • 點擊連結 <a href="...">
  • 送出表單 <form method="GET">
  • 背景轉譯 <link rel="prerender" href="...">

這幾個情況有兩個共通點:都是 GET 且皆會觸發網頁跳轉(Navigation),如此一來就能避免 Strict 需要重新登入的問題,也不會在瀏覽其他網站時毫不知情的送出 Cookie。

Lax + POST

然而為了避免破壞某些現有的登入流程,Chrome 目前在 SameSite=Lax 放寬了一點限制,給開發者更多時間喘息。

在 Cookie 被設定的兩分鐘內,無論 Request Method 是甚麼,只要觸發 Top-level 頁面跳轉都會帶上 Cookie,也就是讓瀏覽器換了頁面,例如送出表單 <form method="POST">

詳情請見關於 Lax + POST 的討論串

None

想要送出 Third-party cookie 就必須設定為 SameSite=None; Secure,沒錯,現在起想要在測試環境送出 Third-party cookie 請準備 https://localhost

另外以 XHR/Fetch 送出 Cross-Origin Request 需要另外設定 withCredentials: true 才會帶上 Cookie 和讓 Response header 的 Set-Cookie 生效,而 Server 端要在 Response header 中設定 Access-Control-Allow-Credentials: true,JavaScript 才能存取 Response 的內容。

不支援的瀏覽器

並不是所有瀏覽器都已經支援最新的 SameSite 規則,因此可以在 Server 加入一些暫時的 Workaround 來支援多種瀏覽器:

兩種 Cookie 都設

此種方式幾乎可以解決所有瀏覽器的問題,缺點就是 Cookie 都會變成兩份:

Set-cookie: name=value; SameSite=None; Secure
Set-cookie: name-legacy=value; Secure

Server 端的程式碼:

if (req.cookies['name']) {
  // 有新的就用新的
  cookieVal = req.cookies['name'];
} else if (req.cookies['name-legacy']) {
  // 不然就用舊的
  cookieVal = req.cookies['name-legacy'];
}

User Agent

以 Request 的 User agent 判斷瀏覽器來決定 Set-Cookie 的內容,這種方式只需要修改設定 Cookie 的程式碼,不用修改 Parse 的部分,但這種判斷方式相對變數較多,比較容易設定成錯誤的 Cookie 。

 

回顧

  • 沒設 SameSite 屬性的 Cookie 都會變成 SameSite=Lax,Cross-site 環境下無法送出。
  • 想要 Cross-site 送出 Cookie 需要設定 SameSite=None; Secure
  • 可以用 SameSite sandbox 測試目前用的瀏覽器是否符合最新的 SameSite 規則。

 

Credits

Understanding "same-site" and "same-origin"
SameSite cookies explained
SameSite cookie recipes
SameSite sandbox


上一篇
[Day 25] Performance - Analyze Runtime Activities
下一篇
[Day 27] Cross-Origin Resource Sharing (CORS)
系列文
你所不知道的各種前端 Debug 技巧30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言