REST API向け、メモ程度です。
環境
- ruby 2.5.3
- rails 5.2.2
- 環境変数 ALLOWED_ORIGINSにカンマ区切りで許可したいホスト名を指定
ALLOWED_ORIGINS="localhost,localhost:3000,somehost..."
1. routesでOPTIONSを受け入れるようにする
CORS preflight requestを受け入れられるように routes.rbを編集します。
routes.rb
Rails.application.routes.draw do
# 中略
get 'some_api' => 'some_api#some_api'
# この設定例はあらゆるパスに対するOPTIONSを受け入れるので適宜修正してください
match '*path', {
controller: 'some_api',
action: 'preflight',
constraints: { method: 'OPTIONS' },
via: [:options]
}
end
2. CORS preflight requestに応答できるようにする
some_api_controller.rb
class SomeApiController < ActionController::API
before_action :ensure_request_from_allowed_origin, only: [:some_api]
def preflight
set_preflight_headers!
head 204
end
def some_api
# CORS対象にしたいAPI
# 正しいContent-Typeを指定しなければCross-Origin Read Blocking (CORB)でブロックされるので注意
"some response"
end
private
# CORS対象にしたいAPIのヘッダを設定。filterとしてbefore_actionで指定できるようにする例
def ensure_request_from_allowed_origin
origin_host = get_request_host
return unless origin_host.present?
head 403 and return unless is_request_from_allowed_origin(origin_host)
# 許可されたホストからのみAccess-Control-Allow-Originヘッダを付与して応答します
# 特定METHODのみ許可するならMETHODのチェックもここで行ってください
response.headers.merge!(
{
'Access-Control-Allow-Origin' => request.headers['Origin']
}
)
end
# リクエストされたホスト名を得る
def get_request_host
origin = request.headers['Origin']
return URI.parse(origin).host if origin.present?
forwarded_host = request.headers['X-Forwarded-Host']
return forwarded_host if forwarded_host.present?
nil
end
# CORSが許可されているホストのアクセスからかどうかを調べます。
# 必要ならスキームのチェックも追加してください。
# 環境変数 `ALLOWED_ORIGINS`にはカンマ区切りでホスト名を指定してください
def is_request_from_allowed_origin(origin_host)
allowed_host_list = ENV['ALLOWED_ORIGINS']
return false unless origin_host.present? && allowed_host_list.present?
hosts = allowed_host_list.split(',')
hosts.include? origin_host
end
# プリフライトレスポンスヘッダを設定
# 許可されていないホストの場合403
# 許可されているホストの場合、`Access-Control-Allow-Origin`を該当ホストに置き換えて応答します
def set_preflight_headers!
origin_host = get_request_host
return unless origin_host.present?
head 403 and return unless is_request_from_allowed_origin(origin_host)
response.headers.merge!(
{
'Access-Control-Max-Age' => 600,
'Access-Control-Allow-Methods' => 'GET', # 対応したい任意のMETHODに置き換えてください
'Access-Control-Allow-Origin' => request.headers['Origin'],
'X-Content-Type-Options' => 'nosniff',
'Vary' => 'Origin' # 複数ホストに許可するために、Originの内容に応じてレスポンスが変化することを明示します。
# ただし、Access-Control-Allow-Originに*を指定する場合は不要です。
}
)
end
end