CORSエラー
CORSとは?
クロスオリジンリソース共有(CORS)は、ブラウザやCapacitorおよびCordovaを動かすWebビューのようなものが、スクリプトから異なるオリジンのリソースへのHTTPおよびHTTPSリクエストを制限するために使用するメカニズムです。これは主にユーザーのデータを保護し、アプリを侵害する可能性のある攻撃を防ぐためのセキュリティ上の理由によるものです。
外部オリジンがCORSをサポートしているかどうかを知るには、サーバーがブラウザがリクエストを許可するための特別なヘッダーを送信する必要があります。
オリジンとは、Ionicアプリまたは外部リソースが提供されるプロトコル、ドメイン、およびポートの組み合わせです。たとえば、Capacitorで実行されているアプリのオリジンは、capacitor://localhost
(iOS)またはhttp://localhost
(Android)です。
アプリが提供されているオリジン(たとえば、ionic serve
を使用したhttp://localhost:8100
)と、リクエストされているリソースのオリジン(たとえば、https://api.example.com
)が一致しない場合、ブラウザの同一オリジンポリシーが適用され、リクエストを行うにはCORSが必要です。
CORSエラーは、クロスオリジンリクエストが行われたが、サーバーが応答に必要なヘッダーを返さない場合(CORS対応ではない場合)に、Webアプリでよく発生します。
XMLHttpRequestは、https://api.example.comを読み込めません。リクエストされたリソースに「Access-Control-Allow-Origin」ヘッダーが存在しません。したがって、オリジン「http://localhost:8100」はアクセスを許可されていません。
CORSの仕組み
プリフライト付きリクエスト
デフォルトでは、Webアプリがクロスオリジンリクエストを試みると、ブラウザは実際のリクエストの前にプリフライトリクエストを送信します。このプリフライトリクエストは、外部リソースがCORSをサポートしているかどうか、および実際のリクエストを安全に送信できるかどうかを確認するために必要です。これはユーザーデータに影響を与える可能性があるためです。
次の条件の場合、ブラウザによってプリフライトリクエストが送信されます。
- メソッドが
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- または、次のヘッダー以外のヘッダーがある場合
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- または、
Content-Type
ヘッダーが次以外の場合- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- または、
ReadableStream
またはXMLHttpRequestUpload
のイベントリスナーが使用されている場合。
上記のいずれかの条件が満たされている場合、OPTIONS
メソッドを使用したプリフライトリクエストがリソースURLに送信されます。
Content-Type
がapplication/json
のPOST
リクエストを、架空のJSON APIであるhttps://api.example.com
に送信すると仮定しましょう。プリフライトリクエストは次のようになります(明確にするために一部のデフォルトヘッダーは省略されています)。
OPTIONS / HTTP/1.1
Host: api.example.com
Origin: http://localhost:8100
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
サーバーがCORSに対応している場合、Access-Control-Request-*
ヘッダーを解析し、http://localhost:8100
からカスタムのContent-Type
でPOST
リクエストが送信されようとしていることを理解します。
その後、サーバーはAccess-Control-Allow-*
ヘッダーを使用して、このプリフライトに対して、許可されているオリジン、メソッド、およびヘッダーを応答します。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8100
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
返されたオリジンとメソッドが実際のリクエストのものと一致しない場合、または使用されているヘッダーのいずれかが許可されていない場合、リクエストはブラウザによってブロックされ、エラーがコンソールに表示されます。それ以外の場合は、プリフライトの後にリクエストが実行されます。
この例では、APIはJSONを予期しているため、すべてのPOST
リクエストにはContent-Type: application/json
ヘッダーがあり、常にプリフライトされます。
単純なリクエスト
一部のリクエストは、送信しても常に安全と見なされ、次のすべての条件を満たしている場合はプリフライトは不要です。
- メソッドが
- GET
- HEAD
- POST
- これらのヘッダーのみを持つ
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Content-Type
ヘッダーは- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
ReadableStream
またはXMLHttpRequestUpload
のイベントリスナーは使用されていません。
この例のAPIでは、GET
リクエストはJSONデータが送信されないためプリフライトする必要がなく、アプリはContent-Type: application/json
ヘッダーを使用する必要もありません。これらは常に単純なリクエストになります。
CORSヘッダー
サーバーヘッダー(応答)
ヘッダー | 値 | 説明 |
---|---|---|
Access-Control-Allow-Origin | origin または* | http://localhost:8100 のように許可されるオリジンを指定するか、すべてのオリジンを許可する場合は* を指定します。 |
Access-Control-Allow-Methods | methods | リソースへのアクセス時に許可されるメソッド。GET 、HEAD 、POST 、PUT 、DELETE 、CONNECT 、OPTIONS 、TRACE 、PATCH 。 |
Access-Control-Allow-Headers | headers | プリフライトリクエストへの応答で使用され、単純なヘッダー(常に許可されます)に加えて、実際のリクエストを行うときにどのヘッダーを使用できるかを示します。 |
Access-Control-Allow-Credentials | true またはfalse | リクエストをクレデンシャル付きで行うことができるかどうか。 |
Access-Control-Expose-Headers | headers | ブラウザがアクセスできるヘッダーを指定します。 |
Access-Control-Max-Age | seconds | プリフライトリクエストの結果をキャッシュできる時間を示します。 |
ブラウザヘッダー(リクエスト)
ブラウザは、プリフライトリクエストを含むすべてのリクエストで、CORSに適切なヘッダーをサーバーに自動的に送信します。以下のヘッダーは参照用であり、アプリのコードで設定すべきではないことに注意してください(ブラウザは無視します)。
すべてのリクエスト
ヘッダー | 値 | 説明 |
---|---|---|
Origin | origin | リクエストのオリジンを示します。 |
プリフライトリクエスト
ヘッダー | 値 | 説明 |
---|---|---|
Access-Control-Request-Method | method | 実際のリクエストを行うときにどのメソッドが使用されるかをサーバーに知らせるために使用されます。 |
Access-Control-Request-Headers | headers | 実際のリクエストを行うときに使用される、単純ではないヘッダーをサーバーに知らせるために使用されます。 |
CORSエラーの解決策
A. 制御するサーバーでCORSを有効にする
正しく最も簡単な解決策は、Webサーバーまたはバックエンドから適切な応答ヘッダーを返し、プリフライトリクエストに応答することでCORSを有効にすることです。これにより、AngularでXMLHttpRequest
、fetch
、またはHttpClient
のような抽象化を引き続き使用できます。
Ionicアプリは異なるオリジンから実行される可能性がありますが、Access-Control-Allow-Origin
ヘッダーには1つのオリジンしか指定できません。したがって、リクエストのOrigin
ヘッダーの値を確認し、レスポンスのAccess-Control-Allow-Origin
ヘッダーにそれを反映させることをお勧めします。
Access-Control-Allow-*
ヘッダーはすべてサーバーから送信する必要があり、アプリのコードには含まれないことに注意してください。
Ionicアプリが提供される可能性のあるオリジンを以下に示します。
Capacitor
プラットフォーム | Origin |
---|---|
iOS | capacitor://localhost |
Android | http://localhost |
Capacitorの設定でデフォルトを変更した場合は、localhost
をご自身のホスト名に置き換えてください。
Cordova上のIonic WebView 3.xプラグイン
プラットフォーム | Origin |
---|---|
iOS | ionic://localhost |
Android | http://localhost |
プラグインの設定でデフォルトを変更した場合は、localhost
をご自身のホスト名に置き換えてください。
Cordova上のIonic WebView 2.xプラグイン
プラットフォーム | Origin |
---|---|
iOS | http://localhost:8080 |
Android | http://localhost:8080 |
プラグインの設定でデフォルトを変更した場合は、ポート番号8080
をご自身のポート番号に置き換えてください。
ブラウザでのローカル開発
コマンド | Origin |
---|---|
ionic serve | http://localhost:8100 または http://YOUR_MACHINE_IP:8100 |
npm run start または ng serve | Ionic Angularアプリの場合、http://localhost:4200 |
複数のアプリを同時に提供している場合は、ポート番号がより高くなる可能性があります。
Access-Control-Allow-Origin: *
で任意のオリジンを許可すると、すべてのシナリオで動作することが保証されますが、サーバーがリソースへのアクセスを制御する方法や、セッションやクッキーの使用方法によっては、CSRF攻撃などのセキュリティ上の問題が発生する可能性があります。
さまざまなウェブサーバーおよびアプリサーバーでCORSを有効にする方法の詳細については、enable-cors.org を参照してください。
Express/Connectアプリでは、corsミドルウェアを使用してCORSを簡単に有効にできます。
const express = require('express');
const cors = require('cors');
const app = express();
const allowedOrigins = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:8080',
'http://localhost:8100',
];
// Reflect the origin if it's in the allowed list or not defined (cURL, Postman, etc.)
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Origin not allowed by CORS'));
}
},
};
// Enable preflight requests for all routes
app.options('*', cors(corsOptions));
app.get('/', cors(corsOptions), (req, res, next) => {
res.json({ message: 'This route is CORS-enabled for an allowed origin.' });
});
app.listen(3000, () => {
console.log('CORS-enabled web server listening on port 3000');
});
B.制御できないサーバーでのCORSの回避策
キーを漏洩させないでください!
サードパーティのAPIに接続しようとする場合は、まず、アプリ(クライアント側)から直接使用しても安全かどうか、また、Javascriptコードで簡単に確認できるため、秘密鍵やプライベートキー、資格情報が漏洩しないかどうかをドキュメントで確認してください。多くのAPIは、開発者にサーバーで使用させ、重要な情報やキーを保護するために、意図的にCORSをサポートしていません。
1.ネイティブのみのアプリ(iOS/Android)
Capacitorアプリケーション(推奨)
Capacitorアプリケーションの場合は、Capacitor HTTP APIを使用してください。このAPIは、ネイティブライブラリを使用するためにfetch
とXMLHttpRequest
にパッチを適用します。PWAやローカル開発サーバー(たとえば、ionic serve
経由)などのWebベースのコンテキストにもアプリケーションをデプロイする場合は、これらのシナリオに対してもCORSを実装する必要があることに注意してください。
レガシーCordovaアプリケーション
レガシーCordovaアプリケーションの場合は、Awesome Cordova Pluginsラッパーを使用したHTTPプラグインを使用してください。このプラグインはブラウザでは機能しないため、アプリの開発とテストは常にデバイスまたはシミュレーターで実行する必要があることに注意してください。
import { Component } from '@angular/core';
import { HTTP } from '@awesome-cordova-plugins/http/ngx';
@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class HomePage {
constructor(private http: HTTP) {}
async getData() {
try {
const url = 'https://api.example.com';
const params = {};
const headers = {};
const response = await this.http.get(url, params, headers);
console.log(response.status);
console.log(JSON.parse(response.data)); // JSON data returned by server
console.log(response.headers);
} catch (error) {
console.error(error.status);
console.error(error.error); // Error message as string
console.error(error.headers);
}
}
}
2.ネイティブ + PWA
外部リソースへのリクエストをバイパスし、レスポンスに必要なCORSヘッダーを追加するHTTP/HTTPSプロキシを介してリクエストを送信します。このプロキシは、アプリが行うほとんどのトラフィックをインターセプトするため、信頼できるか、またはユーザーが制御できる必要があります。
また、プロキシが提供されている場合、ブラウザまたはWebViewは元のHTTPS証明書ではなく、プロキシから送信された証明書を受け取ることに留意してください。プロキシを使用するために、コードでURLを書き換える必要がある場合があります。
ご自身のサーバーにデプロイできるNode.js CORSプロキシについては、cors-anywhere を参照してください。本番環境で無料のホストされたCORSプロキシを使用することはお勧めしません。
C.CORSまたはブラウザのウェブセキュリティの無効化
CORSは、(ユーザーデータのセキュリティとアプリへの攻撃を防ぐという)正当な理由のために存在することに注意してください。CORSを無効にしようとすることは不可能であり、賢明ではありません。
iOSのUIWebView
のような古いWebViewはCORSを強制しませんが、非推奨となっており、近いうちに姿を消す可能性が非常に高くなっています。iOSのWKWebView
やAndroidのWebView
(どちらもCapacitorで使用)などの最新のWebViewはCORSを強制し、セキュリティとパフォーマンスが大幅に向上しています。
PWAを開発している場合やブラウザでテストしている場合、Google Chromeで--disable-web-security
フラグを使用したり、CORSを無効にする拡張機能を使用したりすることは、非常に悪い考えです。あらゆる種類の攻撃にさらされ、ユーザーにリスクを負わせることはできず、アプリは本番環境では動作しません。