コンテンツにスキップ

Claude CodeでWebアプリに管理画面を追加してBasic認証でアクセス制限する

このハンズオンはD1ハンズオンGitHub Actions版はこちら)の続編。前回作った匿名一行掲示板に、管理者だけが使える管理画面を追加する。アクセス制限は「合言葉をひとつ決めて、知っている人だけが入れる」という最も手軽な方式で実現する。

本記事は Claude Code を例に進めるが、プロンプトを渡す相手はまとめてエージェントと呼ぶ(Codex など他のコーディングエージェントでも、同じプロンプトで進められる)。

DBを使うWebアプリには、DB管理という問題が付きまとう。掲示板なら「不適切な投稿を削除したい」「スパムを非表示にしたい」といった操作が必要になる。そのたびに SQL を直接叩くのは手間だし、ミスのリスクもある。

こうした管理操作はWebアプリの中に管理画面として用意しておくのが定石。必要な操作をボタンひとつでできるようにしておけば、後の運用がぐっと楽になる。

ただし、管理画面は誰でもアクセスできてはまずい。一般ユーザーが投稿を操作できてしまう。特定の人(管理者)だけが使えるように制限する必要がある。

ということで、このハンズオンでは、前回作った掲示板に以下を追加する。

  • 管理画面/admin):投稿の表示/非表示を切り替えられる
  • アクセス制限:Basic認証(合言葉ひとつ)でその管理画面を守る

掲示板

前回作った掲示板

管理画面

今回作る管理画面

1-2. 合言葉ひとつで守る:Basic認証

Section titled “1-2. 合言葉ひとつで守る:Basic認証”

今回のアクセス制限は、いちばん手軽なBasic認証を使う。仕組みは単純で、サーバーに合言葉(パスワード)をひとつ置いておくだけ(単一共有シークレット方式)。保護されたページを開くと、ブラウザがユーザー名とパスワードを尋ねるダイアログを出し、入力された値をヘッダに載せてサーバーへ送る。サーバーはそれが置いてある合言葉と一致するかを照合する。ユーザー登録もメール認証も不要で、個人を識別することもない。合言葉を知っていれば誰でも入れるし、漏れたら合言葉を変えるしかない。

ブラウザが表示するBasic認証のログインダイアログ

ブラウザが出す Basic 認証のログインダイアログ(ユーザー名 admin + パスワード)。この画面で守るのが今回のゴール

Basic認証はブラウザに昔から組み込まれている標準の仕組みなので、HTML側の実装はゼロ・サーバー側も「ヘッダの値を環境変数と照合するだけ」で済む。防御の強さは「合言葉が推測不能であること」がすべて。自分だけが使う管理画面を守るには、これで十分。

その代わり、ログイン画面はブラウザ固定のダイアログで見た目を変えられず、ログアウトも事実上できない、といった不便もある。これらを解消したい場合や、「メールアドレス単位で制限したい」「一般ユーザーそれぞれにアカウントを持たせたい」といった要件が出てきた場合は、別の守り方がある。最後の章でまとめて案内する。

このハンズオンでやることの全体像。

  1. D1 スキーマ変更(エージェント)
    • posts テーブルに hidden カラムを追加
  2. バックエンド追加(エージェント)
    • 管理用 API を追加、公開側 API で hidden 投稿を除外
  3. フロントエンド追加(エージェント)
    • /admin ページを作成
  4. Basic認証で守る
    • 実装 → デプロイ → ADMIN_PASSWORD 設定 → 動作確認
  5. もっと別の守り方もある(次のステップの案内)

ブラウザでの手作業は環境変数の設定だけ。実装はすべてエージェントにプロンプトで依頼できる。

前提:作業フォルダは前回のハンズオン(D1ハンズオン / GitHub Actions版)で作った ~/claude/my-bbs

このハンズオンのプロンプトは、掲示板の標準構成(投稿テーブル posts/公開API /api/posts)を前提にしている。D1ハンズオンでエージェントが別の名前(messages など)で作っていた場合は、その名前に読み替える。プロンプトに「このアプリの投稿テーブルとAPIパスに合わせて」と添えれば、エージェントが既存コードを読んで合わせてくれる。

2. 【データベース】スキーマ変更

Section titled “2. 【データベース】スキーマ変更”

「投稿を非表示にする」機能を実装するには、投稿ごとの「表示/非表示」状態をDBに保存する必要がある。エージェントに「投稿を非表示にできる管理画面を作りたい」と相談すると、hidden カラムの追加が提案される。ここではその手順を先に進める。

マイグレーションファイルの作成をエージェントに依頼する。

Claude
投稿テーブル(掲示板なら posts)にhiddenカラム(INTEGER型、デフォルト0)を追加するマイグレーションファイルを作成して。
テーブル名は既存のスキーマに合わせて。
ファイル名はmigrations/0002_add_hidden.sqlで

作成されたファイルを確認する。内容はこのようになっているはず。

ALTER TABLE posts ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0;

マイグレーションファイルは wrangler.jsoncmigrations_dir で指定したフォルダに置く。GitHub Actions 方式なら push 時に自動適用される。Git連携方式なら push 前に npx wrangler d1 migrations apply my-bbs-db --remote を手元から実行する。

3. 【バックエンド】管理画面用 API の追加

Section titled “3. 【バックエンド】管理画面用 API の追加”

管理操作のAPIは /api/admin/ 配下に置く。後の章で /api/admin/ 配下をまとめてアクセス制限するため、公開側API(/api/posts)と区別しておくのがポイント。

エージェントに依頼する。

Claude
既存の投稿API(掲示板なら /api/posts)と投稿テーブルに合わせて、以下の3点をfunctions/api/配下に追加・修正して。
エンドポイント名・テーブル名は既存のものに揃えて。
1. GET /api/admin/posts を追加する。hiddenの値にかかわらず全投稿を返す
2. PATCH /api/admin/posts/:id を追加する。リクエストボディのhiddenフィールド(0か1)で投稿テーブルのhiddenカラムを更新する
3. GET /api/posts でhidden=0の投稿だけを返すようにする

公開側の /api/posts からは非表示投稿が消え、管理用の /api/admin/posts では全投稿(非表示も含む)が見える、という分担になる。

4. 【フロントエンド】管理画面の作成

Section titled “4. 【フロントエンド】管理画面の作成”

エージェントに依頼する。

Claude
管理画面(public/admin/index.html)を作って。
GET /api/admin/posts で全投稿を一覧表示して、各投稿の表示/非表示を切り替えられるようにしたい。
一覧はテーブルで、各行にボタンをつけて。
非表示中の投稿はグレーアウトして。
ユーザー入力にはXSS防止の処置を入れる。
シンプルなデザインで。

これで管理画面はできたが、今はまだ誰でも操作できる状態。このまま公開してはいけない。次の章でアクセス制限をかけてからデプロイする。

Basic認証はブラウザに標準で備わっている認証の仕組み。保護されたページを開くと、ブラウザがユーザー名とパスワードを尋ねるダイアログを出す。入力した値はリクエストのたびにヘッダに載ってサーバーへ送られ、サーバー側で照合される。

sequenceDiagram
    participant B as ブラウザ
    participant CF as Cloudflare Pages
    B->>CF: /admin にアクセス
    CF-->>B: 401(認証してね)
    Note over B: ユーザー名とパスワードの<br>ダイアログを表示・入力
    B->>CF: 同じURLに再リクエスト<br>(ユーザー名とパスワードをヘッダに載せる)
    Note over CF: ADMIN_PASSWORD と照合
    alt 一致
        CF-->>B: HTML/JS を返す(ここで初めて画面が見える)
    else 不一致
        CF-->>B: 401(ダイアログを再表示)
    end

ブラウザが全部やってくれるので、HTML側の実装はゼロ。サーバー側も「ヘッダの値を環境変数と比べる」だけで済む。最小の手間でページもAPIもまとめて守れる。

エージェントに依頼する。

Claude
管理画面(/admin 配下)と管理API(/api/admin 配下)へのアクセスに Basic 認証をかけて。
パスワードは環境変数 ADMIN_PASSWORD から読んで。
ユーザー名は admin で固定でよい。

変更をコミット・push する。

Claude
今回の変更をコミットしてpushして

GitHub の Actions タブ、または Cloudflare ダッシュボードの Deployments で進行状況を確認する。

この時点で /admin を開くと、認証ダイアログが出るがまだ入れない。サーバー側にパスワード(環境変数)を設定していないため。次へ進む。

Cloudflare ダッシュボードWorkers & Pages → プロジェクト名 → SettingsVariables and Secrets を開く。

Add をクリックして以下を入力し、Save をクリック。Type は Secret を選んでおくと、後から値が画面に表示されなくなる。

Variable nameValue
ADMIN_PASSWORD任意のパスワード

設定後、デプロイし直す(push するか、ダッシュボードから Retry deployment)と環境変数が反映される。

本番 URL の /admin にアクセスすると、ブラウザの認証ダイアログが表示される。ユーザー名 admin と設定したパスワードを入力すると管理画面に入れる。表示/非表示の切り替えを試し、公開側のページから非表示投稿が消えることも確認する。

APIが守られていることも確認する。ブラウザのシークレットウィンドウで https://掲示板のURL/api/admin/posts を直接開き、認証を求められる(キャンセルすると401になる)ことを確認する。

これで管理画面は Basic 認証で守られた。自分だけが使う管理画面なら、ここで完成としてよい。しばらく使うと不便も見えてくる。ログアウトのボタンがどこにもない(ブラウザを完全に終了するまで認証されたまま)し、ログイン画面の見た目も変えられない。こうした不便を解消したくなったときや、守り方を変えたくなったときの選択肢を、次の章で案内する。

Basic認証は「合言葉ひとつ・最小の手間」の入り口。自分だけが使う管理画面なら、ここまでで十分。ただ、用途が変わると別の方式が向いてくる。この記事では作らないが、見当をつけておくと選べるようになる。

Basic認証(この記事)トークン認証Cloudflare Access
実装・設定の手間最小少ない多い(初回のみ)
ログイン画面ブラウザ固定のダイアログ自前(自由にデザイン可)Google のログイン画面
ログアウト事実上できないできるできる
パスワードマネージャブラウザ依存対応させやすいGoogle に委ねる
アクセス制限の単位合言葉を知っているか合言葉を知っているかメールアドレス単位
守れる範囲ページもAPIもAPI(ページは公開)ページもAPIも
外部サービス不要不要Google Cloud Console が必要
  • ログイン画面を自前で作りたい・ログアウトを付けたい・パスワードマネージャにきれいに対応させたい → トークン認証(合言葉をヘッダで照合する自前実装。仕組みは Basic 認証と同じ「合言葉ひとつ」)
  • メールアドレス単位で許可したい・Google アカウントの2段階認証で守りたいCloudflare Access を使うハンズオン。コードを書かずに「このメールアドレスだけ許可」がかけられる
  • 管理者だけでなく、一般ユーザーそれぞれにアカウント(自分のページ)を持たせたい → 合言葉方式の守備範囲を超える。ユーザーごとのログインキーで本人を見分けるログインキーのアカウント管理や、パスキー+リカバリコードのアカウント認証

合言葉方式(Basic認証)の限界は「誰がアクセスしたか」を区別できないこと。管理者が自分ひとりなら何の問題もないが、複数人で個人を識別したくなったら上の Cloudflare Access やアカウント管理に進むとよい。

  • 合言葉(パスワード)が漏れたら、Variables and SecretsADMIN_PASSWORD を変えて再デプロイするだけでよい。単一共有シークレット方式の数少ない利点のひとつ
  • 環境変数の変更はデプロイし直すまで反映されない。push するか Retry deployment
  • 管理者が複数いる場合は同じ合言葉を共有することになる。個人を分けたくなったら Cloudflare Access やアカウント管理へ(→ 6章)