目次
1.予期しないエラー: "CORS Blocked"
2.同一オリジン(Same origin)とクロスオリジン(Cross origin)
3.クロスオリジンリソース共有(CORS)
4.まとめ
予期しないエラー: "CORS Blocked"
ウェブ開発をしてると誰でも一度はブラウザのConsoleパネルで出力されたCORSエラーに出会うことがあります。
Access to fetch at 'https://your-domain.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
初めてこのようなエラーに出会った時、ブラウザのセキュリティーポリシーに対する基本的な理解がなければ、CORSエラーの明確な原因を理解するのが難しくなります。 いつでもCORSに会った時、慌てずに対処できるように簡単にCORSエラーの発生原因と解決方法を説明します。
同一オリジン(Same origin)とクロスオリジン(Cross origin)
CORSが何であるかを理解するためには、まず、ウェブセキュリティの核心原則である 同一オリジンポリシー(SOP, Same origin policy) を理解する必要があります。 同一オリジンポリシーは、ウェブサイトが他のオリジンのリソースに勝手にアクセスすることを防止するブラウザのセキュリティメカニズムであり、ウェブセキュリティ標準です。ブラウザは一般的に同一オリジンポリシーを使用し、他のオリジンのリソースを共有するクロスオリジンを許可しません。 では、ここで言う同一オリジンとクロスオリジンとは一体何を意味するのでしょうか?
HTTPリクエストのヘッダーの中に、出所を意味する「Origin」というヘッダーがあります。これはウェブサイトの「アイデンティティ」や「アドレス」を示す重要なヘッダーです。 この「Origin」ヘッダーは、プロトコル(HTTPやHTTPSなど)、ドメイン(例:developer.mozilla.org
など)、そしてオプションでポート番号で構成されます。
<scheme> "://" <hostname> [ ":" <port> ]
Origin: <https://developer.mozilla.org>
ここでHTTPリクエストを送るオリジンとHTTP応答を送るオリジンが同じOriginを持ってる場合は同一オリジン、そうでない場合はクロスオリジンになります。これをもっと簡単に理解するため、同一オリジンとクロスオリジンの色んな例を見てみましょう。
同一オリジン(Same Origin)
例)01
<http://example.com/app/index.html>
<http://example.com/app/index.html>
この二つのURLは同じスキーム(http
)、同じホスト(example.com
)、そして同じポート(80、デフォルト)を使うので同一オリジンになります。
例)02
<http://Example.com:80>
<http://example.com>
ここで最初のURLはポートを明示的に指定していますが、2番目のURLは指定していません。 しかし、http
のデフォルトのポートは80なので、実質的に両方のURLは同じポートを使うことになります。スキーム(http
)とホスト(example.com
)も同じなので、この2つのURLは同一オリジンになります。 (ドメイン名では大文字と小文字は区別されません)。
例)03
<http://example.com/dir2/index.html>
<http://example.com/dir/index.html>
この二つのURLは同じスキーム(http
)、同じホスト(example.com
)、そして同じポート(80、デフォルト)を使います。ただ、パス(path)が違うだけです。同一オリジンポリシーではパスは考慮されないので、この二つのURLは同一オリジンになります。
クロスオリジン(Cross Origin)
例)01
<http://example.com/app1>
<https://example.com/app2>
最初のURLは http
スキームを、2番目のURLは https
スキームを使います。スキームが異なるため、2つのURLはクロスオリジンになります。
例)02
<http://example.com>
<http://www.example.com>
<http://myapp.example.com>
最初のURLのホストは example.com
です。2番目のURLのホストは www.example.com
です。ウェブでは、サブドメインは別のホストとみなされます。 したがって、www.example.com
とexample.com
は異なるオリジンであり、同一オリジンポリシーに基づき、お互いのリソースにアクセスすることはできません。 3番目のURLのホストは myapp.example.com
です。 これも example.com
とは異なるサブドメインであるため、クロスオリジンになります。
例)03
<http://example.com>
<http://example.com:8080>
最初のURLは明示的なポートがないので、HTTPのデフォルトポートである80を使います。2番目のURLはポート8080を使います。2つのURLは同じスキーム(http
)と同じホスト(example.com
)を持っていますが、ポートが違うのでクロスオリジンになります。
クロスオリジンリソース共有(CORS)
では、どうすればウェブサイトが他のオリジンのリソースに安全にアクセスできるのでしょうか?ここで、クロスオリジンリソース共有(CORS, Cross-Origin Resource Sharing) という別のウェブセキュリティ標準が登場します。CORSはW3Cが定義したウェブセキュリティ標準で、特定の条件と規則の下で同一オリジンポリシーを迂回してウェブサイトが他のオリジンのリソースに安全にアクセスできるように特定のメカニズムを提供します。では、CORSエラーを解決するための具体的な方法として何があるでしょうか?
ブラウザプラグインを使う(開発環境でのみ推奨)
Allow CORS: Access-Control-Allow-Originのようなブラウザプラグインを使用してCORSチェックを無効にする方法があります。この方法はすごく簡単ですが、セキュリティ上のリスク要素があるので、ローカル開発環境で確認するためだけに使いましょう。
直接サーバーのCORSヘッダ設定
一番良い方法はサーバー側の応答ヘッダーに直接CORSヘッダーを追加してCORSを許可することです。 ただ、サーバー側のコードを制御する権限がない場合は使えない方法です。
例) Expressサーバーのコード
const express = require('express')
const request = require('request')
const app = express()
app.use((req, res, next) => { {
res.header("Access-Control-Allow-Origin", "https://www.your-frontend-domain.com")
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); { res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
next()
})
app.listen(3000, () => {
console.log('Proxy server is now running on port 3000')
})
プロキシサーバーを使う
サーバー側コードを制御する権限がないサードパーティーAPIの場合、プロキシサーバーを使うとCORSを迂回することができます。プロキシサーバーは中継サーバーでターゲットAPIを呼び出し、その応答をクライアントに渡す役割をします。 これには、直接プロキシサーバーを構築する方法とプロキシサーバーサービスを利用する方法があります。
直接プロキシサーバーを構築
ユーザーのリクエストを中継してくれるサーバーを直接構築して、このサーバーを通じて目的のAPIにリクエストし、その応答をクライアントに渡す方式です。
例)Expressサーバーのコード
const express = require('express')
const request = require('request')
const app = express()
app.use((req, res, next) => { {
res.header("Access-Control-Allow-Origin", "https://www.your-frontend-domain.com")
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); { res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
next()
})
app.get('/proxy', (req, res) => {
request('WEATHER_API_URL', (error, response, body) => { {
res.send(body)
})
})
app.listen(3000, () => {
console.log('Proxy server is now running on port 3000')
})
プロキシサーバーサービス利用
CORS Anywhereのような外部のプロキシサーバーサービスを利用する方法です。 このようなサービスを利用すれば、サーバー構築に関する負担を減らしながらCORSの問題を解決することができます。
ただ、外部サービスに依存性を持つことになり、大量のリクエストやデータを処理する場合、コストが発生する可能性があります。また、ユーザーのデータが外部サービスを通過することになるので、データのセキュリティとプライバシーに対する考慮が必要なデメリットがあるので、運用環境ではおすすめできません。
まとめ
- ブラウザは基本的に同一オリジンポリシーを使用し、異なるオリジン間のリソースアクセスを制限します。
- 同一オリジンの基準はURLのプロトコル、ホスト、ポートに基づいており、これはHTTP
Origin
ヘッダーを通じて決定されます。 - CORSは、クロスオリジンリソース共有を安全に許可することができるWebセキュリティ標準とメカニズムです。
- 目的に応じて、ブラウザプラグインの使用、CORSヘッダーの設定、またはプロキシサーバーの使用により、CORSエラーを解決することができます。