CORSはAPIを公開する設定ではなく読ませるoriginと認証を分けて決める
公開日: 執筆:佐藤
CORSは、別originにあるweb pageのscriptからAPI responseを読むことを、server側が許可する仕組みです。Access-Control-Allow-Origin: *を付ければ問題が解決する、という設定ではありません。誰でも読める公開dataなのか、特定のsiteだけが使うdataなのか、loginした利用者の情報を返すのかを分け、許可するoriginと認証を別に決めます。
originは、scheme、host、portの組です。https://www.example.jpのpageからhttps://api.example.jpへrequestする場合、hostが違うため同じoriginではありません。見た目には同じ会社のdomainでも、browserは別originとして扱います。WordPress site本体と予約system、shop、会員page、外部の管理画面を組み合わせる場合は、どのpageのscriptがどのAPIを読むかを書き出すところから始めます。
W3CのCORS勧告は、resource側がAccess-Control-Allow-Origin response headerで特定originを許可できることを定めています。CORSはbrowserへ「このoriginからのscriptにresponseを渡してよい」と知らせる仕組みです。APIが何を返すか、誰がloginできるか、送信内容をserverで受け付けるかは、CORSとは別にserver側で確認します。
公開dataと個別dataは同じheaderで扱わない
最初に、API responseを読める相手を三つに分けます。誰が読んでもよい公開情報、特定の自社siteだけに読ませる情報、loginした利用者ごとに内容が変わる情報です。dataの種類が違えば、Access-Control-Allow-Originへ書く値と、認証の必要性も同じにはなりません。
| APIが返す内容 | 読ませる相手 | CORSで考える範囲 | CORS以外に確認すること |
|---|---|---|---|
| 営業時間や公開商品一覧 | 不特定のsite | *を許可する必要が本当にあるか |
公開してよい内容だけを返しているか |
| 自社の予約page用の空き状況 | 決めた自社origin | 許可するoriginを正確に指定する | API URL、更新権限、送信内容の検査 |
| 会員の注文履歴や管理data | loginした利用者 | credentialsを伴うoriginを限定する | login、権限、session、保存先、操作記録 |
説明用の仮例として、https://reserve.example.jpの予約画面から、https://api.example.jpの空き状況APIを読む場合を考えます。空き状況を誰でも読める設計にするか、予約画面だけに読ませる設計にするかを先に決めます。後者なら、Access-Control-Allow-Originへ予約画面のoriginを指定します。https://example.jp、https://www.example.jp、http://reserve.example.jpは同じ値ではありません。実際にscriptを置くoriginを一字ずつ確認します。
*は、どのoriginからも読ませる意思を表します。社外へ公開してよい時刻表や一般向けの在庫表示なら、公開範囲として選ぶ理由があります。しかし、開発中だから、errorを早く消したいから、という理由だけで個別dataへ*を付けません。公開するAPIと、限定した相手に使わせるAPIを同じ設定で処理すると、あとでどこまで読ませているか分からなくなります。
credentialsを伴うrequestはoriginを正確に指定する
cookieやHTTP authenticationのようなcredentialsを伴うCORS requestでは、W3C勧告はAccess-Control-Allow-Originに*を使わず、requestのoriginと一致する値を返すことを求めています。さらにresource側がcredentialsをサポートすることをAccess-Control-Allow-Credentials: trueで示す場合があります。これは、利用者ごとに違うresponseを、任意のsiteのscriptへ読ませないための条件です。
ここで混同しやすいのは、cookieがあることと、APIへ読ませてよいことです。login済みの利用者がbrowserを開いていても、serverはrequestした利用者にそのdataを返してよいかを認証と権限で判断します。CORS headerは、そのresponseを呼び出したpageのscriptへ渡してよいかをbrowserへ示します。片方だけでは、会員dataや管理dataを守れません。
たとえば、管理画面をhttps://admin.example.jp、APIをhttps://api.example.jpへ分ける場合、admin.example.jpだけを許可する設定にします。requestのOrigin headerを受け取ってそのまま反射する実装にはせず、あらかじめ決めた許可originの一覧と照合します。test site、local development、旧domainを一時的に許可する必要があるなら、対象、期限、削除する担当者を記録します。使わなくなったoriginを残すほど、設定を読む人が許可範囲を判断できなくなります。
OPTIONS requestは本番requestの前に何を確かめるかを分ける
CORS requestのうち、methodやrequest headerの条件によっては、browserが本番requestの前にOPTIONS requestを送ります。これはpreflight requestと呼ばれ、serverがそのorigin、method、headerを許可するか確かめるためのrequestです。POSTを使うから必ず同じ動きになるわけではありません。送るheader、content type、credentialsなどの条件を含め、browserが何を送るかを実際に確認します。
preflightで見るべきものと、本番requestで見るべきものを一緒にしない方が、errorの原因を追いやすくなります。preflightでは、Origin、Access-Control-Request-Method、Access-Control-Request-Headersと、serverが返すAccess-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headersを確認します。本番requestでは、status code、response body、認証、権限、入力dataの検査を確認します。
formの内容を送るAPIでCORS errorが出たとき、headerを緩くすれば送れるようになるとは限りません。API URLの間違い、HTTPSの証明書、loginの期限、CSRF対策、server側の入力検査、responseの形式など、別の原因もあります。CORSだけを解除してerror表示を消す前に、browserのnetwork記録でpreflightと本番requestのどちらで止まったかを見ます。
APIの所有とCORSの設定場所を残す
CORSをどこで設定するかは、APIをどこで運用しているかで変わります。applicationのresponseにheaderを付ける場合もあれば、web server、CDN、proxyで付ける場合もあります。同じAccess-Control-Allow-Originが二重に付いたり、古いCDN設定が残ったりすると、管理画面では直したつもりでもbrowserのresponseは変わりません。
外部の制作者にAPI連携を頼むときは、CORSを「domainをまたいで使えるようにする設定」とだけ伝えません。API domain、呼び出すorigin、公開するpath、HTTP method、credentialsの有無、設定する場所、変更できるaccount、確認日を一覧にします。作業を頼むことと、APIやdomainの所有権を渡すことは別です。契約や作業が終わっても、自社が設定を確認できるaccountと記録を残します。
導入時は、次の順で一組ずつ確かめます。
- API pathごとに、返すdataが公開、限定、個別のどれかを決める。
- 呼び出すpageのscheme、host、portを確認し、許可originを固定する。
GET、POSTなど必要なmethodとrequest headerを決め、不要なmethodを広げない。- browserでpreflightと本番requestを確認し、response headerと認証結果を別に記録する。
- test originや旧domainを許可した場合は、削除日と担当者を残す。
最初の一手は、一つのAPI pathを選び、返すdata、呼び出すorigin、利用者の権限、設定を置く場所を四行で書くことです。CORS errorを消すことではなく、読ませる相手を自分で説明できる状態にすることが目的です。
関連記事

