SPAでCORSに対応してCookieを使えるようになるまで

はじめに

SPA で Session を使おうとしたら CORS の影響で Cookie を使うまでに苦労したので、簡単にまとめた

前提知識

Cross-Origin Resource Sharing(オリジン間リソース共有) 、略してCORS

Origin とは、例えば https://sat.ne.jp:8080 のような「スキーマ://ドメイン:ポート番号」のこと

CORS とは Origin が異なるリソースの共有を許可する仕組み

逆に言うと「リソースを共有する場合は制約がある」ということ

SPA の場合、フロントエンド(アプリ)とバックエンド(API)で Origin が異なる(場合が多いと思う)

例えば、React サーバを http://localhost:3000 、API サーバを http://localhost:3001 で開発していたら「CORS の制約で API が呼べない、Session や Cookie が使えない」で困った、というお話

検証環境

説明に関係するものだけ

環境 ライブラリ バージョン 備考
フロントエンド(アプリ) React 18.2.0 typescript で使用
axios 0.27.2 APIクライアント
openapi-generator 6.0.1 コードジェネレータ
バックエンド(API) Go 1.19
Chi 5.0.7 HTTPルータ
oapi-codegen 1.11.0 コードジェネレータ

対応内容

以下の4つの対応をした。

  1. axios で withCredentials=true を設定する
  2. API サーバでプリフライトリクエストに対応する
  3. API サーバで Cookie のセキュリティ対応をする
  4. Go の http.ResponseWriter には書き込み順序がある

axios で withCredentials=true を設定する

axios はデフォルトでは Cookie を扱わない。ブラウザの Cookie を API サーバに送らないし、レスポンスの Set-Cookie も処理しない

axios に Cookie を送受信させたいときは withCredentials=true を設定する

具体的には下のようにした

postLogin() は openapi-generator で生成したメソッド。axios を素で使う場合はこうなる?(やったことない)

フロントエンドの対応はこれだけ

API サーバでプリフライトリクエストに対応する

プリフライトリクエストとは、Origin の異なる相手にリクエストを行う前に、安全な相手かどうかを確認するリクエストのこと

CORS の仕組みのひとつ(と思う)

フロントエンドから API を呼ぶと「プリフライトリクエスト → APIリクエスト」のふたつのリクエストが API サーバに送信される

API サーバがプリフライトリクエストにレスポンスしないと、ブラウザは危険な相手と思って API リクエストを送信しない。つまり API を使えない

なので、API サーバでプリフライトリクエストを処理しなければならない

具体的には下のようにした

Cookie を受け取るには API サーバも AllowCredentials: true で許可しなければならない

なお、AllowCredentials: true とする場合は AllowedOrigins をワイルドカード * だけにするのは禁止されている、という点だけ注意(http://* とかは構わないらしい)

API サーバで Cookie のセキュリティ対応をする

これも CORS の制約で、セキュアでない Cookie はブラウザに無視される

対応方法は以下のどちらか

  1. API サーバを HTTPS にする
  2. Cookie を SameSite=NoneSecure=true にする

API サーバを HTTPS にする

API サーバを HTTPS にするにはhttp.Handle() ではなくて http.ListenAndServeTLS() でサーバを始める

なお、API のコード(codegen.HandlerFromMux() とか)は oapi-codegen で生成したもの

なお、証明書と鍵(cert.pem, key.pem)は ↓ で生成した

Cookie を SameSite=NoneSecure=true にする

HTTP の場合は Cookie に SameSite=NoneSecure=true を設定しないと Set-Cookie がブラウザに無視される

Go の http.ResponseWriter には書き込み順序がある

w.Header().Set()w.WriteHeader()w.Write() の順番でないといけない

Set-Cookie も w.Header().Set() なので、w.WriteHeader()w.Write() よりも前にしておかないと、レスポンスヘッダに書き込まれない

処理の最後に Cookie をしていたらレスポンスヘッダに Set-Cookie がなくて???となってたら順序の問題だと知って脱力した…

おわりに

以上の対応をすることで SPA で API を通じて Cookie を扱うことができるようになった

つまづく箇所が複数あってすべての正解にたどり着くのに苦労したので、ネットの情報の寄せ集めではあるが備忘録としてまとめた