# 前端如何管理 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.com
跟https://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 的問題都可以迎刃而解。
- 輕鬆理解 Ajax 與跨來源請求 (opens new window)
- 原來 CORS 沒有我想像中簡單 (opens new window)
- 跨來源資源共用 - MDN (opens new window)
- 和 CORS 與 Cookie 打交道 (opens new window)
# 跨站偽造請求 (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 之類的方式實作來達到。
為了防止這種情形,主要有幾種解法:
- 每次在發送請求時,都先確認是否有 CSRF token 的值,如果是用 form 的話,通常會塞一個
<input type="hidden" name="csrf_token" value="mytoken" />
,發送到後端後除了檢查 cookie 也檢查是否有 csrf token 值 - 後端在回傳 Set-Cookie 的時候加入
SameSite
這個 attribute
# 前端實作
對前端開發來說,如果你的應用與 API 來源是同一個網域的話,那沒有任何問題,API call 好 call 滿就可以了。
但是如果你的 API 網域跟應用不同的話就要小心了,尤其是在用 cookie、session 當作驗證機制的時候。
舉例來說,你的應用網域是 www.myapp.com
,而 API 的網域則是 api.app.com
,由於兩個並不是同一個 origin,所以在呼叫 API 的時候並不會把 cookie 送出去:
- 呼叫 POST
api.app.com/auth
- 成功回傳
Set-Cookie
domain - 呼叫其他
api.app.com
的 API - 咦?Cookie 怎麼沒有送出去
在實作上,跨域的 cookie 是沒辦法互相存取的,但如果像上面的 case,儘管 api.app.com
沒辦法存取 www.myapp.com
,但仍然可以存取 api.app.com
的 cookie。只是要注意幾點:
- 瀏覽器會自動拒絕沒有
Access-Control-Allow-Credential
的回應 Access-Control-Allow-Origin
不能是 wildcardfetch
要記得加入{ credentials: 'include' }
;ajax
則要記得加入{ withCredentials: true }
的選項
# 結論
雖然大多數的工夫都是在後端的實作,不過我認為知道這些東西是怎麼運作的可以幫助你更快除錯,也知道如果問題發生時該找後端還是調整參數,以上這幾個議題都還可以深入探討,想要瞭解更多的話可以參考 MDN 或是 TechBridge 上的文章。
另外和 CORS 與 Cookie 打交道 (opens new window)這篇文章,除了 medium 和我的部落格之外,剩下都是抄襲,也不知道為什麼這篇莫名其妙被抄走,總之還請大家注意一下。
雖然晚了一天,但還是祝大家中秋節快樂!好想吃月餅跟蛋黃酥啊。