コンテンツにスキップ

Claude CodeでWebサイトにBasic認証をかけて合言葉を知っている人だけに見せる

公開したサイトを、全員には見せたくないことがある。作りかけを関係者だけに見せたい。身内向けのページを作った。管理用の画面を自分だけが開けるようにしたい。

こういうとき、合言葉をひとつ決めて、知っている人だけを通すのがいちばん手軽。Basic認証という、ブラウザに昔から備わっている仕組みを使う。

この記事では2ページだけの小さなサイトを作り、サイト全体にかける場合一部だけにかける場合の両方を試す。

ログイン機能というと大がかりに聞こえるが、Basic認証は画面を作らない。ログインフォームを実装する必要も、パスワードをデータベースに保存する必要もない。ブラウザが合言葉を尋ねる画面を出してくれるので、こちらは「合っているか照合する」だけで済む。

そのぶん割り切りもある。誰がアクセスしたかは区別できないし、ログアウトもできない。それでも「関係者だけに見せる」用途にはこれで足りることが多い。もっと本格的な守り方が要るようになったときの選択肢は、最後の章で案内する。

必要な前提は「Cloudflareにサイトを公開できること」だけ。データベースも Git も使わない。

2ページだけのサイトを作る。

public/
├─ index.html 誰でも見られるトップ
└─ secret/index.html 合言葉を知っている人だけ

トップページは誰でも開ける。

「だれでも見られるページ」と書かれたシンプルなページ

/secret/ を開こうとすると、ブラウザが合言葉を尋ねてくる。

ブラウザが表示するログインダイアログ。ユーザー名とパスワードの入力欄がある

正しく入れると中身が見える。

「合言葉を通過しました」と表示された鍵のかかったページ

このダイアログは自分で作るものではない。 ブラウザが勝手に出す。だから HTML 側の実装はゼロで済む。

まずはサイトを作る。ここはCloudflareにHTMLをアップロードして公開すると同じ話なので、さっと済ませる。

Finder で作業用のフォルダを新しく作る。たとえば ホームclaudemy-secret-site

次に Claudeデスクトップアプリを起動し、Code(Claude Code)を選択 → New session → いま作ったフォルダを指定する(~/claude/my-secret-site)。

そこで次のように頼む。

Claude
このフォルダに public フォルダを作って、2ページだけの静的サイトを作ってください。
- public/index.html は誰でも見られるトップページ
- public/secret/index.html は、あとで合言葉で守るページ
- 中身は見分けがつけばよく、凝らなくていい
- トップページから secret のページへのリンクを置いてください

途中でファイル操作の許可を求められたら、その都度許可する。

できあがったら public/index.html をダブルクリックして、ブラウザで表示を確認しておく。この時点ではどちらも普通のページで、鍵はかかっていない。

鍵をかける前に、まず普通のサイトとして公開する。この段階では2ページとも誰でも見られる。そこから鍵をかけるので、何が変わったのかがはっきり分かる。

Cloudflare にログインしていなければ済ませておく。

Terminal window
npx wrangler login

次にプロジェクト名を決める。これがそのまま公開URL(https://名前.pages.dev)になる。

*.pages.dev は Cloudflare 全体で早い者勝ち。 すでに使われている名前を指定すると、末尾にランダムな文字が付く(my-sitemy-site-abc)。決める前にブラウザで https://候補名.pages.dev を開いてみると、空いているかどうかが分かる。

この記事では my-secret-site として書き進めるが、そのまま使うと他の読者と衝突するので、自分で決めた名前に読み替えてほしい。

名前が決まったら、設定ファイルの用意からデプロイまでまとめて頼む。

Claude
public フォルダを Cloudflare Pages に「my-secret-site」という名前で公開して。
wrangler の設定ファイル(wrangler.jsonc)も作ってほしい。
- 公開ディレクトリは public
- compatibility_date は今日の前日の日付にする

compatibility_date を前日にするのは、UTC 基準で「今日」が未来日と判定されてエラーになるのを避けるため。

wrangler.jsonc が作られ、プロジェクトが登録され、デプロイまで進む。裏ではこの2つのコマンドが動く。

Terminal window
npx wrangler pages project create my-secret-site --production-branch main
npx wrangler pages deploy

公開できたら https://my-secret-site.pages.dev を開いてみる。この時点では /secret/ も誰でも見られる。 ここに次の章で鍵をかける。

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 というヘッダを付けること。これを見たブラウザが、自動でダイアログを出してくれる。

合言葉を確かめる処理を作ってもらう。仕組み自体は短いので、どこに何を置くか比較のしかただけ伝えれば足りる。

Claude
/secret/ 以下に Basic 認証をかけて。
- 合言葉は環境変数 SITE_PASSWORD から読む
- ユーザー名は環境変数 SITE_USER から読む(既定は guest)
- タイミング攻撃を避けるため、比較は crypto.subtle.timingSafeEqual を使う

できあがるのは1ファイルだけ。この置き場所が大事なので、次の章で説明する。

functions/secret/_middleware.js

合言葉をコードに書いてはいけない。 ファイルにも書かない。Cloudflare に預ける。

Terminal window
npx wrangler pages secret put SITE_PASSWORD

打つとその場で入力を求められる。貼り付けても画面に表示されず、ターミナルの履歴にも残らない。

いっぽうユーザー名は秘密ではない。こちらは設定ファイルに平文で書いてよいので、頼んでしまう。

Claude
Basic認証のユーザー名を guest にして。
これは秘密ではないので、設定ファイルに直接書いてかまわない

wrangler.jsonc にこう書かれる。

"vars": {
"SITE_USER": "guest"
}

秘密かどうかで置き場所を変える、というのがここでの考え方。何でもかんでも厳重に扱おうとすると手間が増えて、結局どこかで手を抜くことになる。自分の手が必要なのは、本当に秘密のものだけ。

4-4. 鍵をかけたあと、もう一度デプロイする

Section titled “4-4. 鍵をかけたあと、もう一度デプロイする”

合言葉を預けただけでは反映されない。 もう一度デプロイして、はじめて有効になる。

Terminal window
npx wrangler pages deploy

/secret/ を開くと合言葉を聞かれ、トップページは今までどおり誰でも開ける。

5. 置き場所が、鍵をかける範囲を決める

Section titled “5. 置き場所が、鍵をかける範囲を決める”

ここがこの記事の本題になる。

さきほど作ったファイルは functions/secret/_middleware.js だった。_middleware.js という名前のファイルは、そのディレクトリ以下へのアクセス全部に対して、ページを返す前に割り込む。

だから置き場所を変えるだけで、鍵のかかる範囲が変わる

置き場所鍵がかかる範囲
functions/secret/_middleware.js/secret/ 以下だけ
functions/_middleware.jsサイト全体(静的ファイルも含む)

サイト全体にかけたければ、ファイルを移動してデプロイし直すだけでいい。中身は1文字も変えない。 これも頼めば済む。

Claude
Basic認証をサイト全体にかけたい。
ミドルウェアの置き場所を変えて、デプロイし直して

裏で動くのはこれだけ。

Terminal window
mv functions/secret/_middleware.js functions/_middleware.js
npx wrangler pages deploy

実際に両方で確かめると、こうなる。

置き場所/(トップ)/secret/
functions/secret/_middleware.js誰でも見られる合言葉が要る
functions/_middleware.js合言葉が要る合言葉が要る

作りかけのサイトを丸ごと隠したいときは全体に、一部のページだけ隠したいときはそのディレクトリに置く。この使い分けだけ覚えておけばいい。

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 は常に同じ時間で比べるので、この手が使えなくなる。

ユーザー名と合言葉を両方とも必ず比較しているのも同じ理由。片方が違った時点で打ち切ると、「どちらが違ったか」が応答の速さから分かってしまう。

いまは合言葉がひとつだけ。adminguest を分けたい、といったことはよくある。自分用と、人に渡す用。

環境変数に複数の組を並べて、順に照合すれば増やせる。

Claude
Basic認証のユーザーを複数にしたい。
環境変数 SITE_USERS に "admin:合言葉1,guest:合言葉2" の形で並べて、
どれかに一致したら通すようにして。
どのユーザーで入ったかを console.log に残してほしい

合言葉が混じるので、これも預ける形にする。

Terminal window
npx wrangler pages secret put SITE_USERS

増やすと2つ、できることが増える。

  • 誰が入ったか分かる。 ユーザー名はリクエストのたびに送られてくるので記録できる
  • 個別に止められる。 環境変数から1行消してデプロイし直すだけ

Basic認証は「合言葉ひとつ・最小の手間」の入り口。関係者だけに見せる用途なら、ここまでで十分。ただ、使っていると不便も見えてくる。

ログアウトができない。 一度通すと、ブラウザを完全に終了するまで認証されたままになる。ログアウトボタンを付けようがない。

ログイン画面の見た目を変えられない。 あのダイアログはブラウザのもので、こちらからは手を出せない。

誰がアクセスしたかを区別できない。 合言葉を知っているかどうかだけなので、複数人に配ると誰が見たのか分からない。合言葉を変えるときも全員に配り直すことになる。

いっぽうで、思ったより便利な点もある。ブラウザのパスワードマネージャがそのまま使える。

Chrome が「パスワードを保存しますか?」と提案しているダイアログ

通過すると、ブラウザが保存を提案してくる。保存しておけば次からは自動で入るので、長くて複雑な合言葉にしても困らない。自前でログイン画面を作ると、パスワードマネージャに認識させるのに手間がかかることがあるが、Basic認証はブラウザ標準の仕組みなので、こちらは何もしなくていい。

用途が変わってきたら、別の方式が向いてくる。この記事では作らないが、見当をつけておくと選べるようになる。

Basic認証(この記事)トークン認証Cloudflare Access
実装・設定の手間最小少ない多い(初回のみ)
ログイン画面ブラウザ固定のダイアログ自前(自由にデザイン可)Google のログイン画面
ログアウト事実上できないできるできる
アクセス制限の単位合言葉を知っているか合言葉を知っているかメールアドレス単位
外部サービス不要不要Google Cloud Console が必要

合言葉方式の限界は「誰がアクセスしたか」を区別できないこと。自分ひとり、あるいは信頼できる少人数なら何の問題もないが、個人を識別したくなったら上に進むとよい。

  • 合言葉が漏れたら、npx wrangler pages secret put SITE_PASSWORD で入れ直してデプロイするだけでよい。単一の合言葉方式の数少ない利点
  • 合言葉を変えてもデプロイし直すまで反映されない
  • 掲示板などのデータを扱うアプリに管理画面を付けて守る例は、管理画面をBasic認証で守るにある。この記事の内容を、実際のアプリに当てはめた形になっている