# 前端如何管理 API (中)- Cookie、CORS 、CSRF

在實作 API 的時候,如果你的應用有牽扯到使用者認證,那麼跟這三個主題絕對脫不了關係,今天就來淺談這幾個話題

註:並不是所有驗證方式都是用 cookie 實作,有時也會用 Authorizaion 這個 header 來實作。

CORS 與 cookie 在前端是個蠻重要的問題,不過大多數在開發的時候,因為前後端的 domain 時常是相同的,所以很少去 care 這些問題。或者只要要求後端將 Access-Control-Allow-Origin: * 開好開滿就對了,很少去理解背後運作的機制。

針對這個問題,MDN (opens new window) 上其實有個非常詳盡的解說,所以這篇文章主要在於整理重點以及在實際操作上時常發生的問題。

# 同源政策(same-origin policy)

為了防止 javascript 在網頁上隨地撒野,同源政策規定了某些特定的資源、程式碼,必須在同源的情況下才可以存取。

那麼,什麼是同源呢?一份 document 的來源,由 protocol, host, port 來定義。

如果文件 1 來自 http://kalan.com,而文件 2 來自於 https://kalan.com 他們就不算同源;那如果是 subdomain 呢?像是 https://api.foobar.comhttps://app.foobar.com。因為他們的 host 不同,所以也不算同一個 origin。

不過有些資源是本來就能夠透過跨來源取得的:

  • <img />
  • <video />, <audio />
  • <iframe />:可以透過定義 header 來防止他人嵌入
  • 透過 <link rel="stylesheet" href /> 載入的 CSS 腳本
  • <script src="" /> 載入的 Javascript

但透過程式碼發出的跨來源請求則會受到同源政策的限制(如 Fetch, XHR)。

如果都要限制在同源政策下的話,前後端開發會非常難以進行,也沒辦法用 XHR 的方式套用其他 SDK 的 API。也因此出現了 CORS(Cross-Origin Resource Sharing)的機制。

# 什麼是 CORS?

簡單來講就是瀏覽器為了讓不同 origin 的資源也可以共享,所以在有條件的狀況下,放寬了對跨域資源的限制。

不過要實作 CORS 最主要還是在於後端的實作,而最重要的大概就是 Access-Control-Allow-Origin 這個 header 了,只要有這個 header,並且值跟發送請求的 domain 符合,或是 * 就可以成功取得回應。

# 什麼是預檢請求(Preflight Request)

在一些情況下,你的請求會先發送一個 OPTIONS 的請求(由瀏覽器自動發起的),確認通過條件後才會將真正的請求送出,這個就叫預檢請求。

關於這兩個主題,在 TechBrige 與 MDN 上有相當詳盡的解說,這邊就不多加贅述了,如果不想看到 console 上出現紅紅的字或是盲目調參數的話,強烈建議把整個 CORS 的介紹給看完,這樣幾乎所有 CORS 的問題都可以迎刃而解。

# 跨站偽造請求 (Cross-Site Request Forgery 簡稱 CSRF)

舉例來說,如果有個 email 上寫著 click me to get account,但其實這個連結可能是個 delete 的 API,像是:api.app.com/delete,點擊後因為瀏覽器存有 cookie,cookie 會自動發出去,所以你的文章、使用者身份可能就這樣被意外刪除了。

這聽起來很可怕,但實際上是有可能發生的。

你可能會問,2019 年了還有哪個後端工程師會把 delete 用 GET method,但其實 CSRF 不只存在於 GET 而已,也可以用 form 之類的方式實作來達到。

為了防止這種情形,主要有幾種解法:

  1. 每次在發送請求時,都先確認是否有 CSRF token 的值,如果是用 form 的話,通常會塞一個 <input type="hidden" name="csrf_token" value="mytoken" />,發送到後端後除了檢查 cookie 也檢查是否有 csrf token 值
  2. 後端在回傳 Set-Cookie 的時候加入 SameSite 這個 attribute

# 前端實作

對前端開發來說,如果你的應用與 API 來源是同一個網域的話,那沒有任何問題,API call 好 call 滿就可以了。

但是如果你的 API 網域跟應用不同的話就要小心了,尤其是在用 cookie、session 當作驗證機制的時候。

舉例來說,你的應用網域是 www.myapp.com,而 API 的網域則是 api.app.com,由於兩個並不是同一個 origin,所以在呼叫 API 的時候並不會把 cookie 送出去:

  1. 呼叫 POST api.app.com/auth
  2. 成功回傳 Set-Cookie domain
  3. 呼叫其他 api.app.com 的 API
  4. 咦?Cookie 怎麼沒有送出去

在實作上,跨域的 cookie 是沒辦法互相存取的,但如果像上面的 case,儘管 api.app.com 沒辦法存取 www.myapp.com,但仍然可以存取 api.app.com 的 cookie。只是要注意幾點:

  • 瀏覽器會自動拒絕沒有 Access-Control-Allow-Credential 的回應
  • Access-Control-Allow-Origin 不能是 wildcard
  • fetch 要記得加入 { credentials: 'include' }ajax 則要記得加入 { withCredentials: true } 的選項

# 結論

雖然大多數的工夫都是在後端的實作,不過我認為知道這些東西是怎麼運作的可以幫助你更快除錯,也知道如果問題發生時該找後端還是調整參數,以上這幾個議題都還可以深入探討,想要瞭解更多的話可以參考 MDN 或是 TechBridge 上的文章。

另外和 CORS 與 Cookie 打交道 (opens new window)這篇文章,除了 medium 和我的部落格之外,剩下都是抄襲,也不知道為什麼這篇莫名其妙被抄走,總之還請大家注意一下。

雖然晚了一天,但還是祝大家中秋節快樂!好想吃月餅跟蛋黃酥啊。