個人のアプリケーションを開発において、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属性を付与するために必要
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
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
- GitHub - cyu/rack-cors: Rack Middleware for handling Cross-Origin Resource Sharing (CORS), which makes cross-origin AJAX possible.
- GitHub - pschinis/rails_same_site_cookie: Manages the new SameSite=None behavior for Rails apps that use cookie-based authentication for cross-domain requests
- Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita
- Rails と Rack - Railsガイド
バックエンド: Go(Echo)での対応例
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)
...
}
- CORS Middleware | Echo - High performance, minimalist Go web framework
- CORS Recipe | Echo - High performance, minimalist Go web framework
- add SameSite support by srenatus · Pull Request #28 · alexedwards/scs · GitHub
フロントエンドで対応すべきこと
クロスオリジン間で資格情報(クッキー等)を送信するために、リクエスト時に特定のフラグを設定する必要がある。
XMLHTTPRequest や axios では、 withCredentials というフラグを true に設定する。
資格情報を異なるオリジンへ送信している場合のリクエストヘッダー例
Cookie: _sample_key=XXXXXXX
- XMLHttpRequest.withCredentials - Web APIs | MDN
- GitHub - axios/axios: Promise based HTTP client for the browser and node.js
フロントエンド: Nuxt.jsによる対応例
export default {
...
axios: {
baseURL: process.env.API_URL, // APIのエンドポイントのURLは環境変数で指定しておくと開発環境、本番環境で分けられるので便利
credentials: true // withCredentialsがtrueにする
}
}