CORS
クロスオリジンリソースシェアリング(CORS ↗)は、HTTPヘッダーを使用して、あるオリジンで実行されているWebアプリケーションが別のオリジンの選択されたリソースにアクセスする許可を与えるメカニズムです。Webアプリケーションは、自身のオリジンとは異なるオリジン(ドメイン、プロトコル、ポートを含む)のリソースを要求する際に、クロスオリジンHTTPリクエストを実行します。
CORSリクエストがAccessで保護されたサイトに到達するためには、リクエストに有効なCF-Authorizationクッキーを含める必要があります。これはリクエストの種類に応じて追加の設定が必要になる場合があります。
-
シンプルリクエスト ↗は、事前確認リクエストをトリガーせずに、オリジンに直接送信されます。設定手順については、シンプルリクエストを許可するを参照してください。
-
事前確認リクエスト ↗は、実際のリクエストを送信する前にブラウザがOPTIONSリクエストを送信する原因となります。OPTIONSリクエストは、オリジンによって許可されているメソッドとヘッダーを確認します。設定手順については、事前確認リクエストを許可するを参照してください。
Accessで保護されたドメインにシンプルなCORSリクエストを行い、まだログインしていない場合、リクエストはCORSエラーを返します。このエラーを解決する方法は2つあります。
- オプション1 — ログインしてページを更新する。
- オプション2 — 認証トークンを自動的に送信するCloudflare Workerを作成する。この方法は、CORS交換に関与する両方のサイトがAccessの背後にある場合にのみ機能します。
- ブラウザでターゲットドメインにアクセスします。Accessのログインページが表示されます。
- ターゲットドメインにログインします。これにより、
CF-Authorizationクッキーが生成されます。 - CORSリクエストを行ったページを更新します。更新により、新しく生成されたクッキーでリクエストが再送信されます。
Accessで保護されたドメインに事前確認のクロスオリジンリクエストを行うと、OPTIONSリクエストは403エラーを返します。このエラーは、ドメインにログインしているかどうかに関係なく発生します。これは、ブラウザがOPTIONSリクエストにクッキーを含めないためです。したがって、Cloudflareは事前確認リクエストをブロックし、CORS交換が失敗します。
このエラーを解決する方法は3つあります。
- オプション1 — オリジンへのOPTIONSリクエストをバイパスする。
- オプション2 - Cloudflareを設定してOPTIONSリクエストに応答させる。
- オプション3 — 認証トークンを自動的に送信するCloudflare Workerを作成する。この方法は、CORS交換に関与する両方のサイトがAccessの背後にある場合にのみ機能します。
Cloudflareを設定して、OPTIONSリクエストを直接オリジンサーバーに送信できます。OPTIONSリクエストのためにAccessをバイパスするには:
- Zero Trust ↗に移動し、Access > Applicationsに進みます。
- OPTIONSリクエストを受信するオリジンを見つけて、Editを選択します。
- Settingsタブで、CORS settingsまでスクロールします。
- Bypass options requests to originをオンにします。これにより、このアプリケーションの既存のCORS設定がすべて削除されます。
Access JWTのためにCORSを強制することは依然として重要です。このオプションは、オリジンサーバーでCORSの強制が確立されている場合にのみ使用するべきです。
Cloudflareを設定して、あなたの代わりにOPTIONSリクエストに応答させることができます。OPTIONSリクエストはオリジンに到達しません。事前確認の交換が解決された後、ブラウザは認証クッキーを含むメインリクエストを送信します(Accessで保護されたドメインにログインしている場合)。
Cloudflareが事前確認リクエストにどのように応答するかを設定するには:
-
Zero Trust ↗に移動し、Access > Applicationsに進みます。
-
OPTIONSリクエストを受信するオリジンを見つけて、Editを選択します。
-
Settingsタブで、CORS settingsまでスクロールします。
-
ダッシュボードのCORS設定 ↗を、オリジンから送信される応答ヘッダーに一致するように設定します。
たとえば、
api.mysite.comが次のヘッダーを返すように設定されている場合:headers: {'Access-Control-Allow-Origin': 'https://example.com','Access-Control-Allow-Credentials' : true,'Access-Control-Allow-Methods': 'GET, OPTIONS','Access-Control-Allow-Headers': 'office','Content-Type': 'application/json',}その場合、Accessで
api.mysite.comに移動し、Access-Control-Allow-Origin、Access-Control-Allow-Credentials、Access-Control-Allow-Methods、およびAccess-Control-Allow-Headersを設定します。
-
Save applicationを選択します。
-
(オプション)
curlを使用してオリジンにOPTIONSリクエストを送信することで、設定を確認できます。たとえば、Terminal window curl --head --request OPTIONS https://api.mysite.com \--header 'origin: https://example.com' \--header 'access-control-request-method: GET'は、次のような応答を返すべきです:
HTTP/2 200date: Tue, 24 May 2022 21:51:21 GMTvary: Origin, Access-Control-Request-Method, Access-Control-Request-Headersaccess-control-allow-origin: https://example.comaccess-control-allow-methods: GETaccess-control-allow-credentials: trueexpect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=A%2FbOOWJio%2B%2FjuJv5NC%2FE3%2Bo1zBl2UdjzJssw8gJLC4lE1lzIUPQKqJoLRTaVtFd21JK1d4g%2BnlEGNpx0mGtsR6jerNfr2H5mlQdO6u2RdOaJ6n%2F%2BS%2BF9%2Fa12UromVLcHsSA5Y%2Fj72tM%3D"}],"group":"cf-nel","max_age":604800}nel: {"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}server: cloudflarecf-ray: 7109408e6b84efe4-EWR
Cloudflare Accessで保護された2つのサイト、example.comとapi.mysite.comの間で行われるリクエストはCORSチェックの対象となります。example.comにログインしたユーザーには、example.com用のクッキーが発行されます。ユーザーのブラウザがapi.mysite.comをリクエストすると、Cloudflare Accessはapi.mysite.comに特有のクッキーを探します。ユーザーがすでにapi.mysite.comにログインしていない場合、リクエストは失敗します。
二重ログインを避けるために、認証情報をapi.mysite.comに自動的に送信するCloudflare Workerを作成できます。
- Workersアカウント
wranglerのインストールexample.comとapi.mysite.comのドメインがAccessで保護されている
新しいAccessサービストークンを生成するためにこれらの手順に従ってください。Client IDとClient Secretを安全な場所にコピーします。後のステップで使用します。
-
Zero Trust ↗に移動し、Access > Applicationsに進みます。
-
api.mysite.comアプリケーションを見つけて、Editを選択します。 -
Policiesタブを選択します。
-
次のポリシーを追加します:
Action Rule type Selector Service Auth Include Service Token
ターミナルを開き、次のコマンドを実行します:
npm create cloudflare@latest -- authentication-workeryarn create cloudflare@latest authentication-workerpnpm create cloudflare@latest authentication-workerこれにより、create-cloudflare ↗パッケージのインストールが促され、セットアップが進められます。
For setup, select the following options:
- For What would you like to start with?, choose
Hello World example. - For Which template would you like to use?, choose
Hello World Worker. - For Which language do you want to use?, choose
JavaScript. - For Do you want to use git for version control?, choose
Yes. - For Do you want to deploy your application?, choose
No(we will be making some changes before deploying).
プロジェクトディレクトリに移動します。
cd authentication-worker/src/index.jsを開き、既存のコードを削除して次の例を貼り付けます:
// あなたのAPIが存在するホスト名const originalAPIHostname = "api.mysite.com";
export default { async fetch(request) { // ホストだけを変更します。リクエストがexample.com/api/nameで来た場合、新しいURLはapi.mysite.com/api/nameになります const url = new URL(request.url); url.hostname = originalAPIHostname;
// あなたのAPIがapi.mysite.com/anyname(パスに"api/"がない場合)にある場合、 // example.com/api/nameの"api/"部分を削除します
// url.pathname = url.pathname.substring(4)
// 最良のプラクティスは、元のリクエストを使用して新しいリクエストを構築することです // すべての属性をクローンします。URLを適用するには、コンストラクタが必要です // 一度Requestが構築されると、そのURLは不変です。 const newRequest = new Request(url.toString(), request);
newRequest.headers.set("cf-access-client-id", CF_ACCESS_CLIENT_ID); newRequest.headers.set("cf-access-client-secret", CF_ACCESS_CLIENT_SECRET); try { const response = await fetch(newRequest);
// 応答をコピーします const modifiedResponse = new Response(response.body, response);
// 既存のクッキーを上書きしないように、応答からset-cookieを削除します modifiedResponse.headers.delete("set-cookie");
return modifiedResponse; } catch (e) { return new Response(JSON.stringify({ error: e.message }), { status: 500, }); } },};次に、WorkerをCloudflareアカウントにデプロイします:
npx wrangler deploy-
Cloudflareダッシュボード ↗にログインし、アカウントを選択してWorkers & Pagesに進みます。
-
新しく作成したWorkerを選択します。
-
Triggersタブで、Routesに進み、
example.com/api/*を追加します。Workerはexample.comのサブパスに配置され、クロスオリジンリクエストを避けます。 -
Settingsタブで、Variablesを選択します。
-
Environment Variablesの下に、次の秘密変数を追加します:
CF_ACCESS_CLIENT_ID=<service token Client ID>CF_ACCESS_CLIENT_SECRET=<service token Client Secret>
Client IDとClient Secretは、サービストークンからコピーします。
- 各変数のEncryptオプションを有効にし、Saveを選択します。
example.comアプリケーションを修正して、すべてのリクエストをexample.com/api/に送信するようにします。
HTTPリクエストは、2つの異なるAccessで保護されたドメイン間でシームレスに機能するはずです。ユーザーがexample.comにログインすると、ブラウザはapi.mysite.comではなくWorkerにリクエストを送信します。WorkerはリクエストヘッダーにAccessサービストークンを追加し、その後リクエストをapi.mysite.comに転送します。サービストークンがService Authポリシーに一致するため、ユーザーはもはやapi.mysite.comにログインする必要がありません。
一般的に、CORSの問題をトラブルシューティングする際には、次の手順を推奨します:
- 問題を説明したHARファイルをキャプチャし、同時に記録されたJSコンソールログ出力を取得します。HARファイルだけでは、クロスオリジンの問題の理由を完全に把握することはできません。
- アプリケーションがすべてのfetchまたはXHRリクエストで
credentials: 'same-origin'を設定していることを確認します。 - スクリプトタグでcross-origin設定 ↗を使用している場合、これらは「use-credentials」に設定する必要があります。