Cloudflare WorkersとCloudflare Accessで認証機能付きのWebサイトをつくる
自分だけが見えるWebサイトがほしい!
製造ビジネステクノロジー部・浅野です。
Claudeに自己学習のためのコンテンツをつくってもらうことが増えました。
HTMLでつくった学習コンテンツをPCでひらいて活用していたのですが、だんだん湧いてきたのが「これスマホでも見られないかな」という想い。
「PCの前には座れないけど、ソファに寝転がってスマホを眺めることならできる」、「外出中のちょっとした待ち時間に学習したい」と感じる場面が増えてきたのでした。
そんな気持ちにあわせた環境を実現するため、今回はCloudflare WorkersとCloudflare Accessを利用して、認証機能付きの「自分だけが閲覧できるWebサイト」を用意してみることにします。
やってみる
前提
- Cloudflareのアカウントを用意しておきます。
- あわせて、Zero TrustのFreeプランに加入しておきます。クレジットカードなど支払い情報の入力が必要ですが、無料で50ユーザー分までのアカウント情報の管理が可能です。今回は自分ひとりが閲覧できるようにセットアップしますが、小規模なチーム向けの利用でも十分対応可能です。
- ashnoa/my-portal-templateに、今回の記事のために作成したWebサイトの雛形があります。
- 具体的なコードを知りたい場合や、自分でもつくってみたい場合にご活用ください。
- 上記リポジトリ側にも、セットアップのための手順が記載されています。
プロジェクト用意〜初回デプロイ
まずはプロジェクトの用意から進めます。Get started - CLI · Cloudflare Workers docsを参考に、ターミナルにて以下のコマンドを実行します。
私がほしいのは各種学習用コンテンツの閲覧が可能な個人用ポータルサイトなので、ここではmy-portalというプロジェクト名としました。
npm create cloudflare@latest -- my-portal
対話形式でプロジェクトの初期設定を行います。設定自体は後からでも変更可能ですが、ここでは一旦シンプルなHello World exampleを選択した上で、静的サイトとして作成しました。
ポイントとして、上記のコマンドで用意されるのはCloudflare WorkersによるWorkerであるという点です。従来は静的サイトであればCloudflare Pagesが多く利用されていましたが、近年は静的アセットのホスティングもWorkers(Static Assets)で行えるようになり、フロントとバックエンドを1つのデプロイに統合しやすいことから、新規プロジェクトでWorkersを採用するケースが増えています。今回の用途(静的サイト+認証用のWorker処理)はWorkersで十分カバーできるため、ここではWorkersを利用する方針としました。
╭ Create an application with Cloudflare Step 1 of 3
│
├ In which directory do you want to create your application?
│ dir ./my-portal
│
├ What would you like to start with?
│ category Hello World example
│
├ Which template would you like to use?
│ type Static site
│
├ Copying template files
│ files copied to project directory
│
├ Updating name in `package.json`
│ updated `package.json`
│
├ Installing dependencies
│ installed via `npm install`
│
├ Do you want to add an AGENTS.md file to help AI coding tools understand Cloudflare APIs?
│ yes agents
│
╰ Application created
╭ Configuring your application for Cloudflare Step 2 of 3
│
├ Installing wrangler A command line tool for building Cloudflare Workers
│ installed via `npm install wrangler --save-dev`
│
├ Retrieving current workerd compatibility date
│ compatibility date 2026-07-08
│
├ Do you want to use git for version control?
│ yes git
│
├ Initializing git repo
│ initialized git
│
├ Committing new files
│ git commit
│
╰ Application configured
╭ Deploy with Cloudflare Step 3 of 3
│
├ Do you want to deploy your application?
│ no deploy via `npm run deploy`
│
╰ Done
────────────────────────────────────────────────────────────
🎉 SUCCESS Application created successfully!
💻 Continue Developing
Change directories: cd my-portal
Deploy: npm run deploy
...
作成されたディレクトリに移動してnpm run deployをすることで、すぐにWebサイトの公開が可能です。npm run devにて、ローカル環境での動作確認もできます。ちなみにこれらスクリプト内部では、CloudflareのCLIであるwranglerが動作するようにpackage.jsonに設定が追加されています。
とりあえずトップページと404 Not Foundの場合のHTMLとCSSを用意し、npm run deployを行いました。初回はCloudflareへのログインが求められるので、認証をして進みます。
Successfully logged in.
🌀 Building list of assets...
✨ Read 4 files from the assets directory /ashnoa/my-portal/public
🌀 Starting asset upload...
🌀 Found 4 new or modified static assets to upload. Proceeding with upload...
+ /favicon.svg
+ /styles.css
+ /404.html
+ /index.html
Uploaded 1 of 4 assets
Uploaded 2 of 4 assets
Uploaded 4 of 4 assets
✨ Success! Uploaded 4 files (0.91 sec)
Total Upload: 0.31 KiB / gzip: 0.22 KiB
Uploaded my-portal (4.77 sec)
Deployed my-portal triggers (1.63 sec)
https://my-portal.your-name.workers.dev
Current Version ID: ...
デプロイが完了しました。表示されたメッセージにデプロイ先のURLが表示されているので、アクセスしてみましょう。

シンプルですが、まずはWebサイトの公開ができました。
認証をつける
Webサイトの公開はできましたが、このままでは全世界の人から見えている状態です。そこで、次はCoudflare Accessを利用してメールアドレスによる認証機能を追加します。
まずはCloudflareのアカウントホームから、WorekrsとPagesをクリックし、ドメインタブを開きます。


現在はプロダクション・プレビューの両方で公開の設定になっています。これらをどちらも制限に切り替えます。
切り替えると、オーディエンス(AUD)とJWK URLが表示されます。プロダクション・プレビューのそれぞれについて、両方とも値を控えておきましょう。控えたのちは取り消しからダイアログを閉じます(ちょっと押すのを躊躇いそうな日本語になっていますが、押して問題ないです)。
ちなみに、プレビュー機能はプロダクションのURLでの公開前に、一時的に利用することが可能なURLです。今回は特に利用しないのですが、念のため併せて制限をかけておきます。


制限に切り替わったら、現状のポリシー設定を確認してみましょう。
プロダクション・プレビューのそれぞれで、ポリシーを管理からアプリケーションの管理に進み、ポリシーを開きます。1件登録されているので確認すると、自身のメールアドレスによるポリシーが設定されていることが確認できます。

この状態で認証がすでに有効化されているはずです。Webサイトにアクセスしてみましょう。

先ほどとは違い、Cloudflare Accessによる認証画面が表示されます。ポリシーに設定されていた自身のメールアドレスを入力して進むと、認証コード入力画面に遷移します。別途メールにより認証コードが送られてきますので、それを入力するとWebサイトを表示することができました。

ちなみにスマホからアクセスしてみると、認証を行ったのち同様にWebサイトを表示することができました。

さらにJWT認証もつけてみる(オプショナル)
ここまでで、目的の「認証つきのWebサイト」は実現されていますが、One-click Cloudflare Access for Workers · Changelogにて述べられている通り、Webサイトのアプリケーション側でJWT認証を行うことでさらにセキュアな状態を保つことができます。これはAccess側で設定ミスをしてしまった場合など、Accessでのチェックをすり抜けてリクエストがWebサイトに到達した場合にも、JWTの内容を検証することで不正なリクエストを弾く多層防御の役割を果たしてくれます。
「個人サイトでどこまでセキュリティを固めるか?」という問いに対する判断はさまざまですが、それほど対応コストがかかるものでもないので、今回はJWT認証の仕組みも追加してみます。
上記にサンプル実装があるため、それを参考にしつつ実装を進めます。
まずは静的サイトの構成から、Workerおよびアセットを利用する構成になるようにwrangler.jsoncを更新します。
なお、JWT認証のためにAUDおよびZero Trustのチームドメインが必要になります。これらは機密情報ではなく公開しても実害は少ないはずですが、環境固有の値ではあるため、できれば公開はせず環境変数などで管理したいところです。環境変数はwrangler.jsoncに直接記載することもできますが、そうなるとリモートリポジトリ側に値を含めて公開してしまいかねないので、ローカルでは.dev.varsというファイルに、Cloudflare上ではSecretに、必要な設定値を管理するようにしました。
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-portal",
"compatibility_date": "2026-07-08",
"main": "src/index.ts", // Worker利用のために追加
"assets": {
"directory": "./public",
"not_found_handling": "404-page",
"binding": "ASSETS", // アセット利用
"run_worker_first": true // 全リクエストでWorkerを先行して実行
},
"observability": {
"enabled": true
},
"upload_source_maps": true,
"compatibility_flags": [
"nodejs_compat"
]
}
Workerで動作させるコードは以下のようにしました。サンプル実装に近いですが、AUDとしてプロダクション・プレビューの両方の環境について確認できるようにしました。JWT認証に失敗すると、403でレスポンスしています。
import { createRemoteJWKSet, jwtVerify } from "jose";
interface Env {
ASSETS: Fetcher;
TEAM_DOMAIN: string;
POLICY_AUD_PROD: string;
POLICY_AUD_PREVIEW: string;
}
let jwks: ReturnType<typeof createRemoteJWKSet> | undefined;
export default {
async fetch(request, env): Promise<Response> {
const token = request.headers.get("cf-access-jwt-assertion");
if (!token) {
return new Response("Missing required Cloudflare Access JWT", { status: 403 });
}
try {
jwks ??= createRemoteJWKSet(new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`));
await jwtVerify(token, jwks, {
issuer: env.TEAM_DOMAIN,
audience: [env.POLICY_AUD_PROD, env.POLICY_AUD_PREVIEW],
});
} catch (error) {
return new Response(`Invalid Cloudflare Access JWT: ${(error as Error).message}`, {
status: 403,
});
}
return env.ASSETS.fetch(request);
},
} satisfies ExportedHandler<Env>;
実装としては上記でOKなので、あとは環境変数の設定を行いましょう。
まず設定すべき値について、以下を確認します。
- チームドメイン
- Zero Trustの設定画面から確認可能です。
AUD- 「認証をつける」で控えておいた値になります。
プロダクション・プレビューのそれぞれの環境向けの値が必要です。
- 「認証をつける」で控えておいた値になります。
上記をローカル環境向けには.dev.varsに記載します。ashnoa/my-portal-templateには、用意のための雛形として.dev.vars.exampleを用意していますので参考にしてください。
本番環境向けには以下のコマンドで設定します。それぞれ対話的に実行されますので、1つずつ順番に行います。
# チームドメインの設定
npx wrangler secret put TEAM_DOMAIN
# プロダクション向けの設定
npx wrangler secret put POLICY_AUD_PROD
# プレビュー向けの設定
npx wrangler secret put POLICY_AUD_PREVIEW
上記実施後、npm run deployでデプロイして反映すればJWT認証の仕組みも完成です。
まとめ
Cloudflare WorkersとCloudflare Accessによって、認証機能付きの「自分だけが閲覧できるWebサイト」を作成しました。
いつも認証機能周りの用意は時間がかかることが多いのですが、Cloudflare WorkersとCloudflare Accessの組み合わせなら非常に効率的かつ安全に目的の環境を作成することができました。また、今回は自分ひとりが使う想定で作成しましたが、Zero TrustのFreeプランでは50ユーザー分までのアカウント情報の管理が可能なので、小規模なグループ・チームでの活用にも繋げやすいかと思います。
つい先日Cloudflare Dropも公開され、ますます気軽にコンテンツの公開が行えるようになりそうなCloudflare。よければぜひみなさまもお試しください。





