0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【完全保存版】Cloudflare Workers + Pages で「APIキーを隠して外部APIを叩く」構築マニュアル(CORS回避・本番公開まで)

Last updated at Posted at 2026-01-28

「HTML/JSから外部API(Reinfolib等)を叩きたいが、APIキーをフロントエンドに書くのは怖いし、CORSエラーで弾かれて動かない…」

この問題を解決するために、Cloudflare Workers を「鍵付きの中継サーバ(プロキシ)」として使い、Cloudflare Pages で安全にサイトを公開する手順をまとめました。

実際に私がハマったポイント(Pagesの404、Consoleの貼り付け制限、CORSの許可リスト設定など)も含めて、「迷わず最後まで完走できる」 手順書として残します。

前回の記事
【建築士×ChatGPT】「1枚のHTML」でツールを作ろうとしたらCORSの闇に落ち、Cloudflare Workersで"裏口"を作って完全勝利した話

この記事のゴール(最終構成)

  • Pageshttps://<PAGES_URL>.pages.dev (HTML公開用)
  • Workershttps://<WORKER_URL>.workers.dev (API中継用)
  • ブラウザ:Workersだけを呼び出す。APIキーはWorkersが裏で付与する。
  • セキュリティ:許可したサイト(Pagesとローカル)以外からのアクセスは拒否する。

1. 準備:Cloudflare Workers(プロキシ)を作る

まず、APIキーを隠し持つ「中継サーバ」を作ります。

1-1. Workers作成

  1. Cloudflare Dashboard にログインし、左メニュー Workers & Pages をクリック。
  2. Create applicationWorkers を選択。
  3. start with Hello World! を選択。
  4. 名前を入力(例:reinfolib-proxy-001)して Deploy

作成後、表示される Worker URL を控えておきます。
https://reinfolib-proxy-001.<あなたのサブドメイン>.workers.dev

001.png


2. APIキーを安全に登録する(Secrets)

コードの中にAPIキーを書くと漏洩します。CloudflareのSecrets機能を使います。
(※Secretsに登録した値は暗号化され、あとから画面で値を見ることができなくなります)

  1. 作成したWorkerを開く。
  2. 上部タブ SettingsVariables and Secrets
  3. Add をクリック。
  4. 以下を入力して Save and Deploy
    • Variable name: RFL_KEY (コード内で使う変数名)
    • Value: (あなたのAPIキー / Subscription-Key)
    • Type: Secret

⚠️注意:保存できない?
Add入力欄に「空の行」が残っているとエラーになります。不要な行はゴミ箱アイコンで消してから保存してください。

002.png


3. Workerのコードを作成(CORS対策済み)

3-1. コード編集

Worker画面上部の Edit code をクリックし、worker.js を開きます。

003.png

3-2. コードを全置換

以下のコードをコピーし、エディタの中身を全て消して貼り付けてください。
「許可リスト(Allowlist)」 方式を採用しており、指定したURL以外からのアクセスを遮断します。

worker.js
export default {
  async fetch(request, env) {
    const u = new URL(request.url);

    // ▼▼▼ CORS設定(ここが重要) ▼▼▼
    const origin = request.headers.get("Origin") || "";
    
    // 許可するURLリスト(末尾のスラッシュは無しで記述)
    const ALLOWED = new Set([
      "http://localhost:8787",             // ローカル開発用
      // "https://<PAGES_URL>.pages.dev"  // ★あとでPagesのURLをここに追加
    ]);

    const isAllowed = origin && ALLOWED.has(origin);

    // 許可されたOriginなら、そのOriginを返す(Echo方式)
    const corsHeaders = isAllowed
      ? {
          "Access-Control-Allow-Origin": origin,
          "Vary": "Origin",
          "Access-Control-Allow-Methods": "GET,OPTIONS",
          "Access-Control-Allow-Headers": "*",
        }
      : {}; 

    // Preflight (OPTIONS)
    if (request.method === "OPTIONS") {
      if (!isAllowed) return new Response("Forbidden", { status: 403 });
      return new Response(null, { status: 204, headers: corsHeaders });
    }
    // ▲▲▲ CORS設定ここまで ▲▲▲

    const api = u.searchParams.get("api");

    // ヘルスチェック(設定確認用)
    if (api === "__health") {
      return new Response(JSON.stringify({ ok: true, hasKey: Boolean(env.RFL_KEY) }), {
        headers: { ...corsHeaders, "content-type": "application/json" },
      });
    }

    if (!api) return new Response("missing api param", { status: 400, headers: corsHeaders });

    // 外部APIへ中継(ここでAPIキーを付与)
    u.searchParams.delete("api");
    const targetUrl = new URL(`https://www.reinfolib.mlit.go.jp/ex-api/external/${api}`);
    for (const [k, v] of u.searchParams) targetUrl.searchParams.set(k, v);

    const res = await fetch(targetUrl.toString(), {
      method: "GET",
      headers: {
        "Ocp-Apim-Subscription-Key": env.RFL_KEY, // Secretから読み込み
      },
    });

    const body = await res.arrayBuffer();
    const headers = new Headers(res.headers);
    // レスポンスにもCORSヘッダを付与
    for (const [k, v] of Object.entries(corsHeaders)) headers.set(k, v);

    return new Response(body, { status: res.status, headers });
  },
};

貼り付けたら右上の Deploy を押します。

エラーが出る場合
Uncaught SyntaxError: Unexpected token ']' 等が出る場合は、カッコ [] {} の対応が崩れています。コピー漏れがないか確認してください。


4. 動作確認(F12コンソールの儀式)

ブラウザだけでWorkerが生きているか確認します。

  1. Chromeで新しいタブを開く(何もないページでOK)。
  2. F12キーConsole タブを開く。

4-1. 貼り付け許可(allow pasting)

Consoleにコードを貼ろうとすると、セキュリティ警告が出ることがあります。その場合は手動で以下を入力してEnter。

allow pasting

006-01.png

4-2. ヘルスチェック実行

以下のコードをConsoleに貼り付けて実行します。(URLは自分のWorkerのものに書き換えてください)

// テスト用コード
fetch("https://reinfolib-proxy-001.<あなたのサブドメイン>.workers.dev/?api=__health")
  .then(r => r.json())
  .then(console.log)
  .catch(console.error);

成功時の表示:
{ok: true, hasKey: true}

006.png


5. HTMLをPagesで公開する

5-1. Pagesプロジェクト作成

  1. Cloudflare Dashboard → Workers & Pages
  2. Create applicationPagesUpload assets
  3. プロジェクト名(例:<あなたのサブドメイン>)を入力して Create project

007.png

5-2. アップロード(404の罠に注意)

「Upload your project assets」で、HTMLが入ったフォルダをドラッグ&ドロップします。

⚠️ 最重要:404エラーを防ぐために
アップロードするフォルダの**一番上(ルート直下)**に index.html がある状態でアップしてください。
フォルダ > フォルダ > index.html のように深い階層になっていると、公開後に 404 Not Found になります。

Deploy site を押し、成功したらURL(例:https://<あなたのサブドメイン>.pages.dev)を控えます。


6. 仕上げ:Workerの許可リストを更新

このままだと、CORS制限によりPagesからAPIを叩けません。Workerに「PagesからのアクセスはOKだよ」と教えてあげます。

  1. Workerの Edit code に戻る。
  2. ALLOWED のリストに、PagesのURLを追加する。
    // 許可するURLリスト
    const ALLOWED = new Set([
      "http://localhost:8787",             // ローカル確認用
      "https://<あなたのサブドメイン>.pages.dev"       // ★追加したPagesのURL(末尾スラッシュなし)
    ]);
  1. Deploy を押す。

最終確認方法

Pagesの公開URLを開き、F12 → Console で以下を入力します。

location.origin

表示されたURL(例:https://<あなたのサブドメイン>.pages.dev)が、Workerのリストと完全に一致していれば成功です!

008.png


補足:ローカル開発の注意点(file://禁止)

PC上のHTMLファイルをダブルクリックで開くと、URLが file://C:/Users/... になります。
この状態だと Origin が null になりやすく、CORSエラーの原因になります。

ローカルで動かす場合は、必ずローカルサーバを立ててください。
(WindowsならHTMLのあるフォルダでPowerShellを開き、以下を実行するのが最短です)

py -m http.server 8787

その後、Chromeで http://localhost:8787 にアクセスすればOKです。

👉 【建築士×GIS】Google Maps上で「雨水の流下経路」を1クリック追跡する“HTML1枚”ツールを作った(国土地理院 標高×道路中心線×Cloudflare Pages)


以上で、「セキュアで、APIキーを隠蔽し、CORSも回避できる」 環境の完成です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?