クロスオリジンリソース共有(CORS ↗)は、HTTP ヘッダーを使い、あるオリジンで動く Web アプリケーションが、別オリジンの指定したリソースにアクセスできるようにする仕組みです。ドメイン、プロトコル、ポートのいずれかが異なるリソースを要求すると、クロスオリジン HTTP リクエストになります。
Access で保護されたサイトに CORS リクエストが届くには、有効な CF-Authorization Cookie が必要です。リクエストの種類によって、追加の設定が必要になることがあります。
-
シンプルリクエスト ↗ は、プリフライトリクエストを出さずに、オリジンへ直接送られます。設定手順は シンプルリクエストを許可する を参照してください。
-
プリフライト付きリクエスト ↗ では、ブラウザーが実際のリクエストの前に OPTIONS リクエストを送ります。OPTIONS リクエストは、オリジンが許可するメソッドとヘッダーを確認します。設定手順は プリフライト付きリクエストを許可する を参照してください。
Access で保護されたドメインへシンプルな CORS リクエストを送り、まだログインしていない場合、CORS error が返ります。次の 2 つの方法で解消できます。
- 方法 1 — ログインしてページを更新する
- 方法 2 — 認証トークンを自動送信する Cloudflare Worker を作成する。この方法は、CORS 交換に関わる両方のサイトが Access の背後にある場合にだけ使えます。
- ブラウザーで対象ドメインを開きます。Access のログインページが表示されます。
- 対象ドメインにログインします。
CF-AuthorizationCookie が発行されます。 - CORS リクエストを出したページを更新します。新しい Cookie 付きでリクエストが再送されます。
Access で保護されたドメインへプリフライト付きクロスオリジンリクエストを送ると、OPTIONS リクエストは 403 エラーを返します。ドメインにログイン済みでも同じです。ブラウザーは設計上、OPTIONS リクエストに Cookie を付けません。そのため Cloudflare がプリフライトリクエストをブロックし、CORS 交換が失敗します。
次の 3 つの方法で解消できます。
- 方法 1 — OPTIONS リクエストをオリジンへバイパスする
- 方法 2 — OPTIONS リクエストへの応答を Cloudflare で設定する
- 方法 3 — 認証トークンを自動送信する Cloudflare Worker を作成する。この方法は、CORS 交換に関わる両方のサイトが Access の背後にある場合にだけ使えます。
Cloudflare が OPTIONS リクエストをオリジンサーバーへ直接送るように設定できます。Access を OPTIONS リクエストでバイパスするには、次の手順を行います。
- Cloudflare ダッシュボード ↗ で Zero Trust > Access controls > Applications を開きます。
- OPTIONS リクエストを受け取るオリジンを探し、Configure を選択します。
- Advanced settings > Cross-Origin Resource Sharing (CORS) settings を開きます。
- Bypass options requests to origin をオンにします。このアプリケーションの既存の CORS 設定はすべて削除されます。
Access JWT に対する CORS の適用は、引き続き重要です。このオプションは、オリジンサーバー側で CORS を適用している場合にだけ使ってください。
Cloudflare が代わりに OPTIONS リクエストへ応答するように設定できます。OPTIONS リクエストはオリジンに届きません。プリフライト交換が終わると、ブラウザーは本リクエストを送ります。こちらには認証 Cookie が付きます(Access で保護されたドメインにログイン済みの場合)。
プリフライトリクエストへの Cloudflare の応答を設定するには、次の手順を行います。
-
Cloudflare ダッシュボード ↗ で Zero Trust > Access controls > Applications を開きます。
-
OPTIONS リクエストを受け取るオリジンを探し、Configure を選択します。
-
Advanced settings > Cross-Origin Resource Sharing (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 を選択します。
-
(任意)
curlでオリジンへ OPTIONS リクエストを送り、設定を確認できます。例:curl --head --request OPTIONS https://api.mysite.com \ --header 'origin: https://example.com' \ --header 'access-control-request-method: GET'次のような応答が返ります。
HTTP/2 200 date: Tue, 24 May 2022 21:51:21 GMT vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers access-control-allow-origin: https://example.com access-control-allow-methods: GET access-control-allow-credentials: true expect-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: cloudflare cf-ray: 7109408e6b84efe4-EWR
Cloudflare Access で保護された 2 つのサイト(example.com と api.mysite.com)がある場合、両者の間のリクエストは CORS チェックの対象です。example.com にログインしたユーザーには、example.com 用の Cookie が発行されます。ブラウザーが api.mysite.com を要求すると、Cloudflare Access は api.mysite.com 専用の Cookie を探します。ユーザーがまだ api.mysite.com にログインしていなければ、リクエストは失敗します。
2 回ログインしなくて済むように、api.mysite.com へ認証情報を自動送信する Cloudflare Worker を作成できます。
- Workers アカウント
wranglerのインストール- Access で保護された
example.comとapi.mysite.comドメイン(HTTP アプリの保護)
こちらの手順 で、新しい Access サービストークンを生成します。後の手順で使うので、Client ID と Client Secret を安全な場所にコピーします。
-
Cloudflare ダッシュボード ↗ で Zero Trust > Access controls > Applications を開きます。
-
api.mysite.comアプリケーションを探し、Configure を選択します。 -
Policies タブを選択します。
-
次のポリシーを追加します。
Action Rule type Selector Service Auth Include Service Token
ターミナルを開き、次のコマンドを実行します。
npm create cloudflare@latest -- authentication-workeryarn create cloudflare authentication-workerpnpm create cloudflare@latest authentication-workercreate-cloudflare ↗ パッケージのインストールを求められ、セットアップが進みます。
セットアップでは、次のオプションを選びます。
- What would you like to start with? では、
Hello World exampleを選びます。 - Which template would you like to use? では、
Worker onlyを選びます。 - Which language do you want to use? では、
JavaScriptを選びます。 - Do you want to use git for version control? では、
Yesを選びます。 - Do you want to deploy your application? では、
Noを選びます(デプロイ前にいくつか変更します)。
プロジェクトディレクトリに移動します。
cd authentication-worker/src/index.js を開き、既存のコードを削除して、次の例を貼り付けます。
// The hostname where your API lives
const originalAPIHostname = "api.mysite.com";
export default {
async fetch(request, env) {
// Change just the host. If the request comes in on example.com/api/name, the new URL is api.mysite.com/api/name
const url = new URL(request.url);
url.hostname = originalAPIHostname;
// If your API is located on api.mysite.com/anyname (without "api/" in the path),
// remove the "api/" part of example.com/api/name
// url.pathname = url.pathname.substring(4)
// Best practice is to always use the original request to construct the new request
// to clone all the attributes. Applying the URL also requires a constructor
// since once a Request has been constructed, its URL is immutable.
const newRequest = new Request(url.toString(), request);
newRequest.headers.set("cf-access-client-id", env.CF_ACCESS_CLIENT_ID);
newRequest.headers.set("cf-access-client-secret", env.CF_ACCESS_CLIENT_SECRET);
try {
const response = await fetch(newRequest);
// Copy over the response
const modifiedResponse = new Response(response.body, response);
// Delete the set-cookie from the response so it doesn't override existing cookies
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 ページを開きます。
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 アプリケーションを変更し、api.mysite.com ではなく example.com/api/ へすべてのリクエストを送るようにします。
これで、Access で保護された 2 つのドメイン間でも HTTP リクエストが切れずに動きます。ユーザーが 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'が設定されていることを確認します。 - script タグで crossorigin 属性 ↗ を使っている場合は、
"use-credentials"に設定します。