在實作 API 的時候,如果你的應用有牽扯到使用者認證,那麼跟這三個主題絕對脫不了關係,今天就來淺談這幾個話題。註:並不是所有使用者驗證方式都是用 cookie 實作
CORS 與 cookie 在前端是個蠻重要的問題,不過大多數在開發的時候,因為前後端的 domain 時常是相同的,所以很少去研究這些問題,或者只要要求後端將 Access-Control-Allow-Origin: *
開好開滿就對了,很少去理解背後運作的機制。
針對這個問題,MDN 上其實有個非常詳盡的解說,所以這篇文章主要在於整理重點以及在實際操作上時常發生的問題。
為了防止 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)的機制。
簡單來講就是瀏覽器為了讓不同 origin 的資源也可以共享,所以在有條件的狀況下,放寬了對跨域資源的限制。
不過要實作 CORS 最主要還是在於後端的實作,而最重要的大概就是 Access-Control-Allow-Origin
這個 header 了,只要有這個 header,並且值跟發送請求的 domain 符合,或是 *
就可以成功取得回應。
在一些情況下,你的請求會先發送一個 OPTIONS 的請求(由瀏覽器自動發起的),確認通過條件後才會將真正的請求送出,這個就叫預檢請求。
關於這兩個主題,在 TechBrige 與 MDN 上有相當詳盡的解說,這邊就不多加贅述了,如果不想看到 console 上出現紅紅的字或是盲目調參數的話,強烈建議把整個 CORS 的介紹給看完,這樣幾乎所有 CORS 的問題都可以迎刃而解。
舉例來說,如果有個 email 上寫著 click me to get account,但其實這個連結可能是個 delete 的 API,像是:api.app.com/delete
,點擊後因為瀏覽器存有 cookie,cookie 會自動發出去,所以你的文章、使用者身份可能就這樣被意外刪除了。
這聽起來很可怕,但實際上是有可能發生的。
你可能會問,2019 年了還有哪個後端工程師會把 delete 用 GET method,但其實 CSRF 不只存在於 GET 而已,也可以用 form 之類的方式實作來達到。
為了防止這種情形,主要有幾種解法:
<input type="hidden" name="csrf_token" value="mytoken" />
,發送到後端後除了檢查 cookie 也檢查是否有 csrf token 值SameSite
這個 attribute對前端開發來說,如果你的應用與 API 來源是同一個網域的話,那沒有任何問題,API call 好 call 滿就可以了。
但是如果你的 API 網域跟應用不同的話就要小心了,尤其是在用 cookie、session 當作驗證機制的時候。
舉例來說,你的應用網域是 www.myapp.com
,而 API 的網域則是 api.app.com
,由於兩個並不是同一個 origin,所以在呼叫 API 的時候並不會把 cookie 送出去:
api.app.com/auth
Set-Cookie
domainapi.app.com
的 API在實作上,跨域的 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 打交道這篇文章,除了 medium 和我的部落格之外,剩下都是抄襲,也不知道為什麼這篇莫名其妙被抄走,總之還請大家注意一下。
雖然晚了一天,但還是祝大家中秋節快樂!好想吃月餅跟蛋黃酥啊。