メインコンテンツへスキップ
バージョン:v8

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-Typeapplication/jsonPOSTリクエストを、架空の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-TypePOSTリクエストが送信されようとしていることを理解します。

その後、サーバーは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-Originoriginまたは*http://localhost:8100のように許可されるオリジンを指定するか、すべてのオリジンを許可する場合は*を指定します。
Access-Control-Allow-Methodsmethodsリソースへのアクセス時に許可されるメソッド。GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH
Access-Control-Allow-Headersheadersプリフライトリクエストへの応答で使用され、単純なヘッダー(常に許可されます)に加えて、実際のリクエストを行うときにどのヘッダーを使用できるかを示します。
Access-Control-Allow-Credentialstrueまたはfalseリクエストをクレデンシャル付きで行うことができるかどうか。
Access-Control-Expose-Headersheadersブラウザがアクセスできるヘッダーを指定します。
Access-Control-Max-Agesecondsプリフライトリクエストの結果をキャッシュできる時間を示します。

ブラウザヘッダー(リクエスト)

ブラウザは、プリフライトリクエストを含むすべてのリクエストで、CORSに適切なヘッダーをサーバーに自動的に送信します。以下のヘッダーは参照用であり、アプリのコードで設定すべきではないことに注意してください(ブラウザは無視します)。

すべてのリクエスト

ヘッダー説明
Originoriginリクエストのオリジンを示します。

プリフライトリクエスト

ヘッダー説明
Access-Control-Request-Methodmethod実際のリクエストを行うときにどのメソッドが使用されるかをサーバーに知らせるために使用されます。
Access-Control-Request-Headersheaders実際のリクエストを行うときに使用される、単純ではないヘッダーをサーバーに知らせるために使用されます。

CORSエラーの解決策

A. 制御するサーバーでCORSを有効にする

正しく最も簡単な解決策は、Webサーバーまたはバックエンドから適切な応答ヘッダーを返し、プリフライトリクエストに応答することでCORSを有効にすることです。これにより、AngularでXMLHttpRequestfetch、またはHttpClientのような抽象化を引き続き使用できます。

Ionicアプリは異なるオリジンから実行される可能性がありますが、Access-Control-Allow-Originヘッダーには1つのオリジンしか指定できません。したがって、リクエストのOriginヘッダーの値を確認し、レスポンスのAccess-Control-Allow-Originヘッダーにそれを反映させることをお勧めします。

Access-Control-Allow-*ヘッダーはすべてサーバーから送信する必要があり、アプリのコードには含まれないことに注意してください。

Ionicアプリが提供される可能性のあるオリジンを以下に示します。

Capacitor

プラットフォームOrigin
iOScapacitor://localhost
Androidhttp://localhost

Capacitorの設定でデフォルトを変更した場合は、localhostをご自身のホスト名に置き換えてください。

Cordova上のIonic WebView 3.xプラグイン

プラットフォームOrigin
iOSionic://localhost
Androidhttp://localhost

プラグインの設定でデフォルトを変更した場合は、localhostをご自身のホスト名に置き換えてください。

Cordova上のIonic WebView 2.xプラグイン

プラットフォームOrigin
iOShttp://localhost:8080
Androidhttp://localhost:8080

プラグインの設定でデフォルトを変更した場合は、ポート番号8080をご自身のポート番号に置き換えてください。

ブラウザでのローカル開発

コマンドOrigin
ionic servehttp://localhost:8100 または http://YOUR_MACHINE_IP:8100
npm run start または ng serveIonic 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 HTTP APIを使用してください。このAPIは、ネイティブライブラリを使用するためにfetchXMLHttpRequestにパッチを適用します。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を無効にする拡張機能を使用したりすることは、非常に悪い考えです。あらゆる種類の攻撃にさらされ、ユーザーにリスクを負わせることはできず、アプリは本番環境では動作しません。

ソース