Edited at

なんとなく CORS がわかる...はもう終わりにする。


概要

Access to XMLHttpRequest at 'http://localhost:8081' from origin 'http://localhost:8080' has been blocked by CORS policy:

Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

( エラー...? CORS policy ... あー前も見たな、あの同一生成なんとかに引っかかってるやつだっけ?ドメインまたぐとダメなやつだっけ...? )

要するに、「なんとなくわかる」の状態で放置していて、結局何もわかっていないからエラーの対処がよくわからない自分が嫌いになりそうだったので、ここに CORS についてまとめます。

同じように自分を嫌いになりかけている誰かのお役に立てれば幸いです。


CORS の読み方

読み方: コルス or シーオーアールエス

Cross-Origin Resource Sharing の略、日本語訳すると「オリジン間リソース共有」。


オリジンとは?

多分、このオリジン (origin) というワードが理解速度を遅くしている気がします。

オリジンに似ている概念にドメイン (domain) があります。ドメインとオリジンの違いを知ることがイメージ湧きやすそう。例は次のような感じです。

ドメインとの違いは、プロトコルとポート番号を含んでいるという点ですね。

origin == protocol + domain + port number


CORS とは?

オリジン間リソース共有 なので、あるオリジンで動いている Web アプリケーションに対して、別のオリジンのサーバーへのアクセスをオリジン間 HTTP リクエストによって許可できる仕組みです。

仕組み的には、サーバー(下の図で言うと domain-b.com)からのレスポンスヘッダーにリソースを共有するためのヘッダーを追加して許可するという感じです。

CORS_principle.png


CORS の必要性


Same-Origin Policy

Web セキュリティの重要なポリシーの一つに Same-Origin Policy (同一オリジンポリシー)があります。

これは、オリジン間のリソース共有に制限をかけるもので、次のような脆弱性を防ぐことを目的としたものです。


  • XSS (Cross Site Scripting)

ユーザーが Web サイトにアクセスすることで不正なスクリプトが Client (Web ブラウザ) で実行されてしまう脆弱性。

被害例は、Cookie 内のセッション情報を抜き取られて不正ログインを行われる、など。


  • CSRF (Cross-Site Request Forgeries)

Web アプリケーションのユーザーが、意図しない処理を Web アプリケーション (Web Server) 上で実行される脆弱性。通称「しーさーふ」。

被害例は、本来はログインしたユーザーしか実行できない記事の投稿処理を勝手にされる、など。

JavaScript の組み込み API で、Ajax 通信を実現する XMLHttpRequest (XHR)Fetch API などは、これらの脆弱性を回避するため、Same-Origin Policy に従います。


具体的な実装

※クライアントサイドでは特にやることはなく、異なるオリジンへのリクエストは以下のような Origin というフィールドが追加されます。

Origin: https://trusted-one.co.jp

サーバーサイドで以下のレスポンスヘッダーを追加します。

Access-Control-Allow-Origin: https://trusted-one.co.jp  // 特定のサイトを許可する

Access-Control-Allow-Origin: * // 全てのサイトを許可する
Access-Control-Allow-Headers "X-Requested-With, Origin, X-Csrftoken, Content-Type, Accept" // この辺は使うフレームワークにより異なるが許可するヘッダーを定義しておく。

※ XHR は特にやることはないですが、Fetch API では cors mode を設定する必要があります。

fetch('https://trusted-api.co.jp', {

mode: 'cors'
});


Simple Request

単純リクエスト (Simple Request) と言われているのは以下のメソッドです。


  • GET

  • POST

  • HEAD


Preflight Request

単純リクエストとは異なり、プリフライトリクエスト (Preflight Request) は、リクエストの始めに OPTIONS メソッドで対象の異なるオリジンにリクエストを送り、実際のリクエストを送っても問題ないか確認します。

該当するリクエストは以下になります。


  • PUT

  • DELETE

  • CONNECT

  • OPTIONS

  • TRACE

  • PATCH

プリフライトリクエストのレスポンスとして、アクセスを許可するメソッドをレスポンスヘッダーに含める必要があります。

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


Cookie も許可する

Cookie の送受信を許可する場合、クライアントサイド・サーバーサイドに実装が必要になります。


  • Client Side: XHR を使う場合

const xhr = new XMLHttpRequest();

xhr.withCredentials = true; // ここを追加。


  • Client Side: Fetch API を使う場合

fetch('https://trusted-api.co.jp', {

mode: 'cors',
credentials: 'include' // ここを追加。
});


  • Server Side

Access-Control-Allow-Origin* は使えないため、オリジンを明示的に指定する必要があります。

Access-Control-Allow-Origin: https://trusted-one.co.jp

Access-Control-Allow-Credentials: true // ここを追加。


おまけ


Fetch API の mode について



  • 'cors': クロスオリジンリソース共有を実行する。


  • 'same-origin': 同一オリジン以外のアクセスはエラーになる。


  • 'no-cors': クロスオリジンリソース共有ができない場合に、エラーとはならず空のレスポンスが返却される。


express で cors を許可する

app.use((req, res, next) => {

res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
res.header('Access-Control-Allow-Methods', 'PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Credentials', true);

if ('OPTIONS' == req.method) {
res.send(204); // 204: No Content
} else {
next();
}
});

cors モジュールを使うともっと簡単に書けます。

import cors from 'cors';

app.use(cors());


参照

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS