LoginSignup
28
17

More than 1 year has passed since last update.

SPAのアプリケーションにおけるCookieの共有について

Last updated at Posted at 2021-09-24

個人のアプリケーションを開発において、SPAにする機会が多いのですが、開発のたびにどのような設定をするんだっけ?と忘れてしまうことが多いです。
SPAの場合、バックエンドとフロントエンドをそれぞれデプロイし、オリジンが異なるケースがほとんどかと思いますが、この場合、知っておくべきこと、設定すべきことが多く忘れてしまいやすいので、バックエンドとフロントエンドに分けてまとめます。

バックエンドで対応すべきこと

リクエストを受け付ける別オリジンを指定する

これによって、レスポンスの Access-Control-Allow-Origin ヘッダーには許可するオリジンが入り返却される。
* で全オリジンを指定すると資格情報を共有できないので注意する。資格情報を伴わないオリジン間のリソース共有であれば、*指定でも(セキュリティ的には問題があるものの)可能である。

以下、https://example.comからのリクエストを受け付けるように指定した場合のレスポンスヘッダー例

Access-Control-Allow-Origin: https://example.com

リクエストを受け付けるHTTPメソッドを指定する

これによって、レスポンスの Access-Control-Allow-Methods ヘッダーには許可するHTTPメソッドが入り返却される。

以下、GET, POST, PATCH, PUT, DELETE のメソッドのリクエストを受け付けるように指定した場合のレスポンスヘッダー例

Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE

資格情報を共有する設定をする

これによって、レスポンスのAccess-Control-Allow-Credentials ヘッダーに true が入り返却され、フロントエンド側でクッキーを取り扱えるようになる。

以下、資格情報を共有できる場合のレスポンスヘッダー例

Access-Control-Allow-Credentials: true

クッキーのSameSite属性をNoneにする

クッキーのSameSite属性には Lax, Strict, None の3つのモードがあるが、クロスオリジン間での共有では None にする必要がある。

以下、クッキーを設定している場合のレスポンスヘッダー例

Set-Cookie: _example_session=XXXXXXX; path=/; secure; HttpOnly; SameSite=None

バックエンド: Railsでの対応例

  • CORSの設定に、rack-cors, Cookieの設定にrails_same_site_cookieを利用する
  • APIモードを利用する場合は、ミドルウェアを追加する。(APIモードではない場合は、require 'rails/all'によって、すべてのミドルウェアがデフォルトで有効になっているため不要。)
# Gemfile
gem 'rack-cors' # CORSの設定に必要
gem 'rails_same_site_cookie', # SetCookieヘッダーにSameSite属性を付与するために必要
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins ENV['FRONT_DOMAIN']
    resource '*', headers: :any, methods: %i[get post patch put delete], credentials: true
  end
end
config/application.rb
module MyApp
  class Application < Rails::Application
    config.load_defaults 6.1

    config.api_only = true
    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
  end
end

バックエンド: Go(Echo)での対応例

main.go
func main() {
    e := echo.New()

    e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
        AllowOrigins:     []string{os.Getenv("FRONT_DOMAIN")},
        AllowMethods:     []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
        AllowCredentials: true,
    }))
  e.POST("/api/login", Login)
    ...
}

func Login(c echo.Context) error {
    ...
    cookie := &http.Cookie{
        Name:     "jwt",
        Value:    token,
        Expires:  time.Now().Add(time.Hour * 72),
        SameSite: http.SameSiteNoneMode, // SameSite属性をNoneにする
        Path:     "/", // ブラウザからクッキーを送信できるようにする
        Secure:   true, // SSL,HTTPSプロトコルによる送信のみを許可する
        HttpOnly: true, // ブラウザのJSによるクッキーの書き換えを禁止する
    }
    c.SetCookie(cookie)
    ...
}

フロントエンドで対応すべきこと

クロスオリジン間で資格情報(クッキー等)を送信するために、リクエスト時に特定のフラグを設定する必要がある。
XMLHTTPRequest や axios では、 withCredentials というフラグを true に設定する。

資格情報を異なるオリジンへ送信している場合のリクエストヘッダー例

Cookie: _sample_key=XXXXXXX

フロントエンド: Nuxt.jsによる対応例

nuxt.config.js
export default {
  ...
  axios: {
    baseURL: process.env.API_URL, // APIのエンドポイントのURLは環境変数で指定しておくと開発環境、本番環境で分けられるので便利
    credentials: true // withCredentialsがtrueにする
  }
}

参考

28
17
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
17