Claude CodeでWebサイトにBasic認証をかけて合言葉を知っている人だけに見せる
公開したサイトを、全員には見せたくないことがある。作りかけを関係者だけに見せたい。身内向けのページを作った。管理用の画面を自分だけが開けるようにしたい。
こういうとき、合言葉をひとつ決めて、知っている人だけを通すのがいちばん手軽。Basic認証という、ブラウザに昔から備わっている仕組みを使う。
この記事では2ページだけの小さなサイトを作り、サイト全体にかける場合と一部だけにかける場合の両方を試す。
本記事の位置付け
Section titled “本記事の位置付け”ログイン機能というと大がかりに聞こえるが、Basic認証は画面を作らない。ログインフォームを実装する必要も、パスワードをデータベースに保存する必要もない。ブラウザが合言葉を尋ねる画面を出してくれるので、こちらは「合っているか照合する」だけで済む。
そのぶん割り切りもある。誰がアクセスしたかは区別できないし、ログアウトもできない。それでも「関係者だけに見せる」用途にはこれで足りることが多い。もっと本格的な守り方が要るようになったときの選択肢は、最後の章で案内する。
必要な前提は「Cloudflareにサイトを公開できること」だけ。データベースも Git も使わない。
1. 作るもの
Section titled “1. 作るもの”2ページだけのサイトを作る。
public/├─ index.html 誰でも見られるトップ└─ secret/index.html 合言葉を知っている人だけトップページは誰でも開ける。
/secret/ を開こうとすると、ブラウザが合言葉を尋ねてくる。
正しく入れると中身が見える。
このダイアログは自分で作るものではない。 ブラウザが勝手に出す。だから HTML 側の実装はゼロで済む。
2. ページを作る
Section titled “2. ページを作る”まずはサイトを作る。ここはCloudflareにHTMLをアップロードして公開すると同じ話なので、さっと済ませる。
Finder で作業用のフォルダを新しく作る。たとえば ホーム → claude → my-secret-site。
次に Claudeデスクトップアプリを起動し、Code(Claude Code)を選択 → New session → いま作ったフォルダを指定する(~/claude/my-secret-site)。
そこで次のように頼む。
このフォルダに public フォルダを作って、2ページだけの静的サイトを作ってください。
- public/index.html は誰でも見られるトップページ- public/secret/index.html は、あとで合言葉で守るページ- 中身は見分けがつけばよく、凝らなくていい- トップページから secret のページへのリンクを置いてください途中でファイル操作の許可を求められたら、その都度許可する。
できあがったら public/index.html をダブルクリックして、ブラウザで表示を確認しておく。この時点ではどちらも普通のページで、鍵はかかっていない。
3. Cloudflareに公開する
Section titled “3. Cloudflareに公開する”鍵をかける前に、まず普通のサイトとして公開する。この段階では2ページとも誰でも見られる。そこから鍵をかけるので、何が変わったのかがはっきり分かる。
3-1. ログインとプロジェクト名
Section titled “3-1. ログインとプロジェクト名”Cloudflare にログインしていなければ済ませておく。
npx wrangler login次にプロジェクト名を決める。これがそのまま公開URL(https://名前.pages.dev)になる。
*.pages.dev は Cloudflare 全体で早い者勝ち。 すでに使われている名前を指定すると、末尾にランダムな文字が付く(my-site → my-site-abc)。決める前にブラウザで https://候補名.pages.dev を開いてみると、空いているかどうかが分かる。
この記事では my-secret-site として書き進めるが、そのまま使うと他の読者と衝突するので、自分で決めた名前に読み替えてほしい。
3-2. 公開する
Section titled “3-2. 公開する”名前が決まったら、設定ファイルの用意からデプロイまでまとめて頼む。
public フォルダを Cloudflare Pages に「my-secret-site」という名前で公開して。wrangler の設定ファイル(wrangler.jsonc)も作ってほしい。
- 公開ディレクトリは public- compatibility_date は今日の前日の日付にする
compatibility_dateを前日にするのは、UTC 基準で「今日」が未来日と判定されてエラーになるのを避けるため。
wrangler.jsonc が作られ、プロジェクトが登録され、デプロイまで進む。裏ではこの2つのコマンドが動く。
npx wrangler pages project create my-secret-site --production-branch mainnpx wrangler pages deploy公開できたら https://my-secret-site.pages.dev を開いてみる。この時点では /secret/ も誰でも見られる。 ここに次の章で鍵をかける。
4. 合言葉で守る
Section titled “4. 合言葉で守る”4-1. Basic認証とは
Section titled “4-1. Basic認証とは”Basic認証はブラウザに標準で備わっている認証の仕組み。保護されたページを開くと、ブラウザがユーザー名とパスワードを尋ねるダイアログを出す。入力した値はリクエストのたびにヘッダに載ってサーバーへ送られ、サーバー側で照合される。
sequenceDiagram
participant B as ブラウザ
participant CF as Cloudflare Pages
B->>CF: /secret/ にアクセス
CF-->>B: 401(認証してね)
Note over B: ユーザー名とパスワードの<br>ダイアログを表示・入力
B->>CF: 同じURLに再リクエスト<br>(ユーザー名とパスワードをヘッダに載せる)
Note over CF: 預けてある合言葉と照合
alt 一致
CF-->>B: ページを返す(ここで初めて中身が見える)
else 不一致
CF-->>B: 401(ダイアログを再表示)
end
ポイントは 401 という返事に WWW-Authenticate というヘッダを付けること。これを見たブラウザが、自動でダイアログを出してくれる。
4-2. 実装を頼む
Section titled “4-2. 実装を頼む”合言葉を確かめる処理を作ってもらう。仕組み自体は短いので、どこに何を置くかと比較のしかただけ伝えれば足りる。
/secret/ 以下に Basic 認証をかけて。
- 合言葉は環境変数 SITE_PASSWORD から読む- ユーザー名は環境変数 SITE_USER から読む(既定は guest)- タイミング攻撃を避けるため、比較は crypto.subtle.timingSafeEqual を使うできあがるのは1ファイルだけ。この置き場所が大事なので、次の章で説明する。
functions/secret/_middleware.js4-3. 合言葉を預ける
Section titled “4-3. 合言葉を預ける”合言葉をコードに書いてはいけない。 ファイルにも書かない。Cloudflare に預ける。
npx wrangler pages secret put SITE_PASSWORD打つとその場で入力を求められる。貼り付けても画面に表示されず、ターミナルの履歴にも残らない。
いっぽうユーザー名は秘密ではない。こちらは設定ファイルに平文で書いてよいので、頼んでしまう。
Basic認証のユーザー名を guest にして。これは秘密ではないので、設定ファイルに直接書いてかまわないwrangler.jsonc にこう書かれる。
"vars": { "SITE_USER": "guest" }秘密かどうかで置き場所を変える、というのがここでの考え方。何でもかんでも厳重に扱おうとすると手間が増えて、結局どこかで手を抜くことになる。自分の手が必要なのは、本当に秘密のものだけ。
4-4. 鍵をかけたあと、もう一度デプロイする
Section titled “4-4. 鍵をかけたあと、もう一度デプロイする”合言葉を預けただけでは反映されない。 もう一度デプロイして、はじめて有効になる。
npx wrangler pages deploy/secret/ を開くと合言葉を聞かれ、トップページは今までどおり誰でも開ける。
5. 置き場所が、鍵をかける範囲を決める
Section titled “5. 置き場所が、鍵をかける範囲を決める”ここがこの記事の本題になる。
さきほど作ったファイルは functions/secret/_middleware.js だった。_middleware.js という名前のファイルは、そのディレクトリ以下へのアクセス全部に対して、ページを返す前に割り込む。
だから置き場所を変えるだけで、鍵のかかる範囲が変わる。
| 置き場所 | 鍵がかかる範囲 |
|---|---|
functions/secret/_middleware.js | /secret/ 以下だけ |
functions/_middleware.js | サイト全体(静的ファイルも含む) |
サイト全体にかけたければ、ファイルを移動してデプロイし直すだけでいい。中身は1文字も変えない。 これも頼めば済む。
Basic認証をサイト全体にかけたい。ミドルウェアの置き場所を変えて、デプロイし直して裏で動くのはこれだけ。
mv functions/secret/_middleware.js functions/_middleware.jsnpx wrangler pages deploy実際に両方で確かめると、こうなる。
| 置き場所 | /(トップ) | /secret/ |
|---|---|---|
functions/secret/_middleware.js | 誰でも見られる | 合言葉が要る |
functions/_middleware.js | 合言葉が要る | 合言葉が要る |
作りかけのサイトを丸ごと隠したいときは全体に、一部のページだけ隠したいときはそのディレクトリに置く。この使い分けだけ覚えておけばいい。
6. 中身を読んでみる
Section titled “6. 中身を読んでみる”4章で作ってもらった functions/secret/_middleware.js を開いてみる。自動で書かれたコードだが、60行ほどしかないので読んでおくと安心できる。押さえておきたいのは3か所だけ。
合言葉が無ければ、401 とヘッダを返す。
return new Response('合言葉が必要です', { status: 401, headers: { 'WWW-Authenticate': 'Basic realm="secret", charset="UTF-8"' },});このヘッダを見て、ブラウザがダイアログを出す。
合っていれば、本来のページを返す。
return next();next() は「割り込みを終えて、先へ進む」という意味。
比較は1文字ずつやらない。
const okUser = timingSafeEqual(user, env.SITE_USER ?? 'guest');const okPass = timingSafeEqual(pass, env.SITE_PASSWORD);if (!okUser || !okPass) return askForPassword();文字列を先頭から順に比べると、合っている文字数が多いほど時間がかかる。この差を何万回も測られると、合言葉を1文字ずつ当てられてしまう。timingSafeEqual は常に同じ時間で比べるので、この手が使えなくなる。
ユーザー名と合言葉を両方とも必ず比較しているのも同じ理由。片方が違った時点で打ち切ると、「どちらが違ったか」が応答の速さから分かってしまう。
7. 合言葉を増やす
Section titled “7. 合言葉を増やす”いまは合言葉がひとつだけ。admin と guest を分けたい、といったことはよくある。自分用と、人に渡す用。
環境変数に複数の組を並べて、順に照合すれば増やせる。
Basic認証のユーザーを複数にしたい。環境変数 SITE_USERS に "admin:合言葉1,guest:合言葉2" の形で並べて、どれかに一致したら通すようにして。どのユーザーで入ったかを console.log に残してほしい合言葉が混じるので、これも預ける形にする。
npx wrangler pages secret put SITE_USERS増やすと2つ、できることが増える。
- 誰が入ったか分かる。 ユーザー名はリクエストのたびに送られてくるので記録できる
- 個別に止められる。 環境変数から1行消してデプロイし直すだけ
8. 守れるもの・守れないもの
Section titled “8. 守れるもの・守れないもの”Basic認証は「合言葉ひとつ・最小の手間」の入り口。関係者だけに見せる用途なら、ここまでで十分。ただ、使っていると不便も見えてくる。
ログアウトができない。 一度通すと、ブラウザを完全に終了するまで認証されたままになる。ログアウトボタンを付けようがない。
ログイン画面の見た目を変えられない。 あのダイアログはブラウザのもので、こちらからは手を出せない。
誰がアクセスしたかを区別できない。 合言葉を知っているかどうかだけなので、複数人に配ると誰が見たのか分からない。合言葉を変えるときも全員に配り直すことになる。
いっぽうで、思ったより便利な点もある。ブラウザのパスワードマネージャがそのまま使える。
通過すると、ブラウザが保存を提案してくる。保存しておけば次からは自動で入るので、長くて複雑な合言葉にしても困らない。自前でログイン画面を作ると、パスワードマネージャに認識させるのに手間がかかることがあるが、Basic認証はブラウザ標準の仕組みなので、こちらは何もしなくていい。
用途が変わってきたら、別の方式が向いてくる。この記事では作らないが、見当をつけておくと選べるようになる。
| Basic認証(この記事) | トークン認証 | Cloudflare Access | |
|---|---|---|---|
| 実装・設定の手間 | 最小 | 少ない | 多い(初回のみ) |
| ログイン画面 | ブラウザ固定のダイアログ | 自前(自由にデザイン可) | Google のログイン画面 |
| ログアウト | 事実上できない | できる | できる |
| アクセス制限の単位 | 合言葉を知っているか | 合言葉を知っているか | メールアドレス単位 |
| 外部サービス | 不要 | 不要 | Google Cloud Console が必要 |
- ログイン画面を自前で作りたい・ログアウトを付けたい → トークン認証(合言葉をヘッダで照合する自前実装。仕組みは Basic認証と同じ「合言葉ひとつ」)
- メールアドレス単位で許可したい・Google アカウントの2段階認証で守りたい → Cloudflare Access を使うハンズオン。コードを書かずに「このメールアドレスだけ許可」がかけられる
- 管理者だけでなく、一般ユーザーそれぞれにアカウントを持たせたい → 合言葉方式の守備範囲を超える。ログインキーのアカウント管理やパスキー+リカバリコードのアカウント認証へ
合言葉方式の限界は「誰がアクセスしたか」を区別できないこと。自分ひとり、あるいは信頼できる少人数なら何の問題もないが、個人を識別したくなったら上に進むとよい。
- 合言葉が漏れたら、
npx wrangler pages secret put SITE_PASSWORDで入れ直してデプロイするだけでよい。単一の合言葉方式の数少ない利点 - 合言葉を変えてもデプロイし直すまで反映されない
- 掲示板などのデータを扱うアプリに管理画面を付けて守る例は、管理画面をBasic認証で守るにある。この記事の内容を、実際のアプリに当てはめた形になっている



