Claude Code でソーシャルログイン(Google・GitHub・X 等)の寄せ書きアプリを作る
この記事では、ソーシャルログイン(Google・GitHub・X・Facebook などの「◯◯でログイン」)で本人を確かめる「みんなの寄せ書き」アプリを作る。ユーザーは好きなサービスのアカウントでログインし、名前と一言を残すと、みんなの一言が並ぶ。登録・投稿・ログアウトまでを、Cloudflare の無料枠(Workers + D1)と各サービスの無料の OAuth だけで自前実装する。
この方式を選ぶ理由
Section titled “この方式を選ぶ理由”「◯◯でログイン」を載せる素直な方法は Cloudflare Access だが、無料枠が50ユーザーで、その先が per-seat(1ユーザーいくら)課金になる。個人やコミュニティの無料アプリでは、人が増えるほどコストが線形に膨らんで使いづらい。
一方、50人の壁は Access という製品の制限であって、外部サービスでログインさせること自体の制限ではない。Google も GitHub も OAuth ログインに課金しないので、OAuth を自分の Worker で実装すれば無料・人数無制限になる。しかも Cloudflare Access が組み込みで対応していない X(Twitter) のようなプロバイダも、自前なら扱える。
自前といっても、OAuth の難所(認可リダイレクト・CSRF 対策の state・トークン交換)は @hono/oauth-providers ライブラリに任せる。プロバイダを差し替えるだけで Google でも GitHub でも X でも同じ形で書けるのがこのライブラリの利点。セキュリティの中核は枯れたライブラリに寄せ、自分はアプリのロジックに集中する。
| 認証の載せ方 | 無料の上限 | コスト構造 | 対応プロバイダ |
|---|---|---|---|
| Cloudflare Access | 50ユーザー | per-seat(人数で増える) | Google/GitHub/Facebook/LinkedIn ほか(X は無い) |
| ソーシャルログイン自前(本記事) | 無制限 | フラット(Workers/D1 の無料枠〜数ドル) | Google/GitHub/X/Facebook/LinkedIn ほか |
1. このハンズオンで作るもの
Section titled “1. このハンズオンで作るもの”1-1. 作るもの:みんなの寄せ書き
Section titled “1-1. 作るもの:みんなの寄せ書き”- 各ユーザーが好きなサービス(Google・GitHub・X など)でログインしてアカウントを作る。
- 名前と一言を入力すると、全員の寄せ書きに並ぶ(1人1投稿、あとから編集可)。
- 閲覧は公開、投稿・編集はログインした本人だけ。ログアウトもできる。
▶ 完成版:(デプロイ後に URL を記載)。この記事はまだ完成版を公開していない。
認証まわりで復旧の仕組み(パスワード再発行やリカバリコード)を自前で持たなくていいのがソーシャルログインの利点。アカウントの回復はログイン先のサービス(Google など)が持っているからだ。
1-2. ソーシャルログイン(OAuth)とは
Section titled “1-2. ソーシャルログイン(OAuth)とは”OAuth 2.0 / OpenID Connect は、「ログインを Google や GitHub のような信頼できる相手に代行してもらう」仕組み。
- ユーザーはそのサービスの画面でログイン・同意し、こちらのアプリにはパスワードを一切渡さない。アプリは「この人はそのサービス上のこのユーザーだ」という結果だけを受け取る。
- だからパスワードを保存しない(漏洩のリスクを持たない)。メール送信の仕組みも要らない。
- アカウントを一意に識別するのは、各サービスが返す不変のユーザーID(Google なら
sub、GitHub なら数値idなど)。メールアドレスではない(後述)。
1-3. 進め方:まず Google だけで作り、あとから増やす
Section titled “1-3. 進め方:まず Google だけで作り、あとから増やす”いきなり全プロバイダを作らず、まず Google だけで一通り動かす。ログイン → 投稿 → ログアウトが動いたら、X や GitHub を1つずつ足していく。自分で作るときもハンズオンでも、この順が一番つまずかない。
プロバイダを増やすときに直すのは、次の3箇所だけ。
- §7-1(ログインルート):そのプロバイダのルートと
normalizeの分岐を1つ足す。 - §9(ボタン):フロントにログインボタンを1つ足す。
- §4(プロバイダ登録):そのサービスの開発者コンソールで OAuth クライアントを作り、シークレットとリダイレクト URI を登録する。
触らないのは §5 スキーマ・§7-2 セッション・§8 寄せ書き API。プロバイダ非依存に作ってあるので、増やしても不変。具体的な足し方は後述の「プロバイダを増やす」節で扱う。
1-4. 個人情報は最小限にする(この記事の設計方針)
Section titled “1-4. 個人情報は最小限にする(この記事の設計方針)”本サイトのハンズオンは、個人情報をできるだけ持たないことを基本方針にしている。理由は、これらを作って運用するのが IT に不慣れな人であることを想定しているから。個人情報を持つほど、漏洩時の被害も、個人情報保護法の観点での責任も重くなる。持たなければ、漏らしようがない。これがいちばん安全。
ソーシャルログインは、この方針と相性がいい。
- 本人を見分けるのに必要なのは
sub(各プロバイダが返す不変のユーザーID)だけ。email は寄せ書きに使わない(一意キーにも使わない)。だから Google に要求するスコープをopenidだけにして、email もプロフィールも取得しない。「保存しない」ではなく「そもそも受け取らない」ところまで踏み込む。 - 表示名も Google の実名を初期値にしない。ログイン直後の名前欄は空にして、本人に入力してもらう。実名を出したい人だけが出せばよく、意図しない実名の公開を防げる。
結果、このアプリが持つ個人に関わる情報は、sub(それ単体では個人に結びつかない仮名の ID)と、本人が自分で決めた表示名だけ。パスワードもメールアドレスも保存しない。
email を後から使いたくなったら:scope に
verified_emailで確認済みかも見られる)。ただし使う当てが無いなら取らないのが原則。必要になってから足す。
以下の §4(スコープ)・§5(スキーマ)・§7(normalize)は、すべてこの方針の実装になっている。
2. OAuth の仕組み・要点
Section titled “2. OAuth の仕組み・要点”実装に入る前に、OAuth の流れと「どこを自分が守るか」を押さえる。
2-1. Authorization Code Flow
Section titled “2-1. Authorization Code Flow”flowchart LR B["ブラウザ"] -->|1 ログイン開始| W["自分の Worker"] W -->|2 プロバイダへリダイレクト| G["Google / GitHub / X …"] G -->|3 同意| B B -->|4 code を持って戻る| W W -->|5 code を情報に交換| G G -->|6 ユーザー情報| W W -->|7 セッション発行| B
- ユーザーが「◯◯でログイン」を押す → Worker がそのプロバイダの認可画面へリダイレクト(
stateを付ける) - ユーザーが同意 → プロバイダがこちらの
redirect_uriに一時的なcodeを付けて戻す - Worker が
codeをclient_secretと一緒に交換し、ユーザー情報を取得 - Worker がユーザーを D1 に作る/引く → 自前のセッション Cookie を発行
この 1〜3 の往復(state の生成・照合=CSRF 対策、code の交換)を @hono/oauth-providers が内部でやってくれる。こちらが書くのは 4 のアプリ側だけで、しかもプロバイダが変わっても 4 の形は同じ。
2-2. ライブラリに任せる部分/自分で守る部分
Section titled “2-2. ライブラリに任せる部分/自分で守る部分”- ライブラリが担保:認可リダイレクト、
state(CSRF 対策)、code→ トークン交換、ユーザー情報の取得。 - 自分で守る:
client_secretを秘密に保つ、redirect_uriを正確に登録、一意キーは(プロバイダ+そのユーザーID)、要求スコープを最小にする(使わない個人情報は取得しない)、セッション Cookie の属性、公開データに内部情報を混ぜない。
3. プロバイダごとの差を先に押さえる
Section titled “3. プロバイダごとの差を先に押さえる”ここが「全部対応」の肝。作るのは Google からだが、増やすとき用にプロバイダの差を先に見ておく。@hono/oauth-providers は同じ書き方で複数プロバイダを扱えるが、返ってくるユーザー情報はプロバイダごとに違う。ただし本記事は §1-4 の方針どおり sub(一意ID)と、本人が入力する表示名しか使わないので、実際に気にするのは「一意 ID がどのフィールドか」だけ。下の表の「メール」「確認フラグ」列は、取ろうと思えば取れる参考情報として載せてあるが、本記事では取得しない。
| プロバイダ | import / ミドルウェア | context key | 一意ID | メール | 確認フラグ | 表示名の元 |
|---|---|---|---|---|---|---|
.../google / googleAuth | user-google | id(sub) | あり | verified_email | name | |
| GitHub | .../github / githubAuth | user-github | id | null のことあり | なし | name か login |
| X (Twitter) | .../x / xAuth | user-x | id | なし | なし | name / username |
.../facebook / facebookAuth | user-facebook | id | あり | なし | name | |
.../linkedin / linkedinAuth | user-linkedin | sub | あり | email_verified | name |
設計上の結論はこう。
- 一意キーは「プロバイダ名 + そのプロバイダのユーザーID」の組にする(
googleの12345とgithubの12345は別人)。 - email は取得しない(寄せ書きに不要・§1-4)。だから「メールが無い/空/未確認」というプロバイダ差を、そもそも気にしなくてよくなる。X がメールを返さないことも問題にならない。
- 表示名は取得しても初期値にしない。本人入力を基本にして、意図しない実名公開を防ぐ。
同じ人でもプロバイダが違えば別アカウント。ある人が Google で入って寄せ書きし、後日 GitHub で入ると、別の新規アカウントになる(ユーザーIDが違うため)。これを1つにまとめる「アカウント連携」は本記事の範囲外(宿題)。まずは「1ログイン=1アカウント」で作る。
4. 事前準備
Section titled “4. 事前準備”このハンズオンは新規プロジェクトとして作る。
- 作業フォルダを作る(例:
~/claude/yosegaki-social)。 - そのフォルダで Claude Code を使える状態にする。
- Cloudflare に wrangler でログイン済みか確認する。
D1 を作る。
npx wrangler d1 create yosegaki-social-db表示される database_id を控えておく。
4-1. Google 側で OAuth クライアントを作る
Section titled “4-1. Google 側で OAuth クライアントを作る”Google のログインを使うには、Google Cloud Console(認証まわりは現在「Google Auth Platform」に整理されている)で OAuth クライアントを1つ作り、クライアント ID とクライアントシークレットを受け取る。以下は 2026 年 7 月時点の実際の画面。
UI は変わりうる:Google のコンソールは頻繁に画面が変わる。ボタン名が違ったら、同じ意味の項目を探して読み替える。
(1) プロジェクトを作る。Google Cloud Console の「新しいプロジェクト」で、プロジェクト名(例:yosegaki-social)を入れて作成する。
新しいプロジェクトを作る
(2) OAuth 同意画面を構成する。新しいプロジェクトでは「Google Auth Platform はまだ構成されていません」と出るので「開始」を押す。
その画面にたどり着けないときは:Google Auth Platform は左メニューの初期表示に出ていないことがある。その場合は、プロジェクトのページを下までスクロールして「すべてのプロダクトを表示」を開くと、その中に「Google Auth Platform」がある。直接開くなら
https://console.cloud.google.com/auth/overview。なお画面上部のプロジェクト選択が (1) で作ったプロジェクトになっているかも確認する(別プロジェクトを見ていると表示が変わる)。
まだ構成されていない状態。「開始」から設定する
構成ウィザードで、アプリ名(例:みんなの寄せ書き)とサポートメールを入れ、対象は 「外部」 を選び、連絡先メールを入れて、ポリシーに同意して作成する。個人の Gmail では「内部」は選べないので「外部」でよい(テストモードで動く)。
アプリ名とサポートメールを入れる。対象は「外部」
構成が完了した状態
(3) テストユーザーを登録する。外部=テストモードのアプリは、原則としてテストユーザーに登録した Google アカウントに限定される。「対象」ページのテストユーザーで「Add users」を押し、自分の Google アカウントのメールを追加しておく(テスト中にログインする人はここに入れる)。
「対象」ページ。ここでテストユーザーを足す。上の「公開ステータス」と「OAuth ユーザー数の上限」もあとで効いてくる
実際の挙動は要確認:手元で試したところ、本記事の最小スコープ(
openidだけ)だとテストユーザー未登録のアカウントでもそのままログインできた(Google がこの制限を厳密に効かせないことがあるようだ)。逆に、アカウントによっては「アクセスをブロック:このアプリは Google の審査プロセスを完了していません」と弾かれることもある。弾かれたら、そのアカウントをテストユーザーに追加すれば入れる。確実にしたいなら、ログインさせたいアカウントは先に登録しておく。
同じ画面の上部にある 「公開ステータス:テスト中」 と 「OAuth ユーザー数の上限」 も見ておく。**テスト中の間はテストユーザーしかアクセスできず、しかも「アプリの確認前の許可済みユーザー数の上限は 100 で、この上限はアプリの全期間でカウントされる」**と書かれている。
ハンズオンを進めるあいだはテストモードのままでよい。ただし作ったアプリを本当に不特定多数へ公開するなら、この画面の「アプリを公開」で本番に切り替える必要がある。切り替えないと、100人を上限に、しかも一人ずつメールアドレスを登録した人しかログインできない。自分のアプリを公開する段になったら、ここに戻ってくること(→ §13)。
(4) OAuth クライアントを作る。「クライアント」→「OAuth クライアントを作成」→ アプリケーションの種類で 「ウェブ アプリケーション」 を選ぶ。
「ウェブ アプリケーション」を選ぶ
名前を付け、承認済みのリダイレクト URI に次を登録する。この段階では localhost だけでよい(本番 URI はデプロイで URL が確定してから §10 で足す)。
http://localhost:8787/api/auth/googleリダイレクト URI に localhost のコールバックを登録
ポートは自分の dev サーバーに合わせる。
wrangler devの既定は 8787 だが、別のプロジェクトで塞がっていると別ポート(8788 など)で起動する。スクリーンショットは 8788 で動かしたときのもの。リダイレクト URI のポートは、実際に開発サーバーが使うポートと完全一致させること。
(5) クライアント ID とシークレットを控える。作成すると クライアント ID が表示される。
作成完了。クライアント ID が表示される(この画面の「JSON をダウンロード」からシークレットも取れる)
⚠️ クライアントシークレットは作成時の1回しか手に入らない。作成直後のダイアログ(上の画面)で「JSON をダウンロード」するか、クライアント詳細でコピーしておく。取り逃したら、クライアント詳細の「クライアント シークレット」で 「シークレットを追加(Add secret)」 を押して新しいものを発行する(Google は既存シークレットを後から表示してくれない)。
受け取った値は秘密なので、コードに書かない。ローカル開発では .dev.vars に、本番では wrangler のシークレットに入れる。どちらも @hono/oauth-providers が c.env から自動で読む。
まずローカル用の .dev.vars に書く。値はクオートで囲まない(囲まないでおくと、次の本番投入でそのまま使い回せる)。
GOOGLE_ID=(クライアント ID)GOOGLE_SECRET=(クライアントシークレット)本番は wrangler のシークレットに入れる。.dev.vars から直接パイプで流し込むのがおすすめ。
grep '^GOOGLE_ID=' .dev.vars | cut -d= -f2- | tr -d '\r\n"' | npx wrangler secret put GOOGLE_IDgrep '^GOOGLE_SECRET=' .dev.vars | cut -d= -f2- | tr -d '\r\n"' | npx wrangler secret put GOOGLE_SECRETnpx wrangler secret put GOOGLE_ID だけを実行すると、値を手入力するプロンプトが出る。それでも設定はできるが、手で貼り付ける経路には事故が多いので、上のパイプ方式を基本にするとよい。
- 値を画面にもシェル履歴にも出さない。
.dev.varsにすでにある正しい値を、そのまま送る - 貼り間違い・貼り忘れが起きない。
cut -d= -f2-は最初の=より後ろを全部取るので、値に=が含まれていても欠けない - クオートと改行を落とす。
tr -d '\r\n"'があるので、.dev.varsをうっかり"…"で囲んでいても、末尾に改行が紛れ込んでも、正しい値だけが入る - AI コーディングエージェントに任せられる。Claude Code などから実行しても、対話プロンプトで止まらない
⚠️ 手入力するなら、値をクオートで囲まない。
.dev.varsのように"…"で囲んだまま入れると、クオートも値の一部として保存され、本番でinvalid_clientエラー(The OAuth client was not found.)になる。.dev.varsはwrangler devがクオートを外して読むので囲ってもよいが、secret putは入力をそのまま保存する点が違う。上のパイプ方式ならこの罠を踏まない。
⚠️ プロンプトが出ないまま「Success」と表示されたら、空文字が入っている。対話プロンプトを出せない環境(AI エージェント経由の実行など)で
npx wrangler secret put GOOGLE_IDを単体で走らせると、入力を待たずに空の値で成功してしまうことがある。シークレットは書き込み専用で、入った値を後から読んで確かめられない(wrangler secret listは名前しか見せない)。上のパイプ方式で入れ直せば上書きできるので、怪しいと思ったら入れ直す。
redirect_uriは登録と完全一致が必須。1文字(ポート・スラッシュ・http/https)でも違うと Google がredirect_uri_mismatchを返す。本番 URL はまだ要らない。プロバイダ側の作業は「いま:アプリ作成+localhost 登録」と「§10 のあと:本番 URI 追加」の2回に分かれる。
GitHub・X・Facebook を足すときも、各社のコンソール(GitHub: Developer settings → OAuth Apps/X: X Developer Portal/Facebook: Meta for Developers)で同じように OAuth アプリを作り、<プロバイダ>_ID / <プロバイダ>_SECRET を足す(→ §12)。
5. 【データベース】スキーマ
Section titled “5. 【データベース】スキーマ”ソーシャルログインならテーブルは2つでよい。肝は users を(プロバイダ+プロバイダ側ユーザーID)で一意にすること。
D1 マイグレーション migrations/0001_init.sql を作って。テーブルは2つ。- users(id TEXT PK=内部用ランダムUUID, provider TEXT=プロバイダ名, provider_user_id TEXT=そのプロバイダのユーザーID, display_name, message, created_at, updated_at)。email は保存しない(使わないので列を作らない) UNIQUE(provider, provider_user_id) を必ず付ける。(provider, provider_user_id) にインデックス- sessions(id TEXT PK=ランダムなセッショントークン, user_id FK→users ON DELETE CASCADE, created_at, expires_at)
補足:- アカウントの同一性は (provider, provider_user_id) の組。初回ログイン時だけ users 行を作る。- email など個人情報は取得・保存しない(§1-4)。同一性は (provider, provider_user_id) だけで判定する。- display_name の初期値は空にする(Google の実名を入れない。本人が入力する)。message とあわせて寄せ書きの表示用。作ったらローカルに適用する。
npx wrangler d1 migrations apply yosegaki-social-db --local6. wrangler.jsonc と Hono の骨組み
Section titled “6. wrangler.jsonc と Hono の骨組み”このアプリは Cloudflare Workers + Static Assets で作り、ルーティングに Hono を使う。
依存を入れる。
npm install hono @hono/oauth-providerswrangler.jsonc を作って。Workers + Static Assets 構成にする。- main は src/index.js- assets は public/ を binding 名 ASSETS で配信- d1_databases に binding "DB"、database_name "yosegaki-social-db"、database_id は d1 create で出た値- compatibility_date は最近の日付、compatibility_flags に "nodejs_compat" を入れる- 各プロバイダの <PROVIDER>_ID / <PROVIDER>_SECRET は wrangler secret で入れる(wrangler.jsonc には書かない)ルーティングは Hono に任せる。/api/* を各ルートで処理し、それ以外はすべて静的ファイル(public/)に委ねる形にしてもらう。
7. 【バックエンド】ソーシャルログインとセッション
Section titled “7. 【バックエンド】ソーシャルログインとセッション”7-1. ログインルート(まず Google だけ)
Section titled “7-1. ログインルート(まず Google だけ)”@hono/oauth-providers のミドルウェアを、プロバイダごとに1本のルートに噛ませる。各ルートが「送り出す」役と「戻ってきた code を処理する」役を兼ねる。戻ってきた後の処理(ユーザー正規化・upsert・セッション)は共通化して、プロバイダが増えても足すのは分岐1つで済むようにする。ここではまず Google だけ作る。
src/index.js に Google のソーシャルログインを実装して。@hono/oauth-providers を使う。あとで他プロバイダを足せる形にする。- ルートは GET /api/auth/google。googleAuth を噛ませる。 client_id / client_secret は c.env.GOOGLE_ID / GOOGLE_SECRET。scope は ["openid"] だけ(email もプロフィールも取得しない。§1-4)。 redirect_uri はこのルートの絶対URL(http://localhost:8787/api/auth/google と本番URL)に一致させる。- プロバイダごとの user を共通形に正規化する関数 normalize(provider, user) を作る(増やすとき、ここに分岐を1つ足すだけにする): 戻り値 { provider, providerUserId }(本人の一意ID だけ。email も表示名も受け取らない) ・Google: user-google → id(sub)を providerUserId にする- 正規化後の共通処理: ・(provider, providerUserId) で users を検索し、無ければ作る(display_name の初期値は空=NULL。Google の実名を入れない)。あれば既存を使う。 ・セッションを発行して Cookie をセットし、トップ(/)へリダイレクト。normalize は「プロバイダの差を1箇所に閉じ込める関数」。いまは Google の分岐だけを持ち、プロバイダを増やすときはここに分岐を1つ足していく(具体例は後述の「プロバイダを増やす」節)。
なぜ表示名を空で作るか。Google の
name(実名のことが多い)を初期値にすると、本人が気づかないまま実名が寄せ書きに公開されうる。だから初期値は空(NULL)にして、本人が入力した名前だけを出す。email を取らないのと同じ「個人情報は最小限」の考え方(§1-4)。scope をopenidだけにしているので、そもそもnameも
7-2. セッションの共通処理
Section titled “7-2. セッションの共通処理”セッションは D1 に持ち、不透明なトークンを Cookie に入れる。Cookie は HttpOnly; Secure; SameSite=Lax。
src/session.js にセッションの共通処理を作って。- createSession(env, userId): ランダムトークンを sessions に保存(expires_at は例えば30日後)。HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age付き の Cookie を返す。- getSessionUser(c): Cookie のトークンで sessions を引き、期限内なら user_id を返す。無効なら null。- deleteSession(c): セッション削除+Cookie失効。- 保護ルート用の認証ミドルウェア requireAuth を用意し、未ログインは 401、成功時は c に user_id を渡す。なぜ
SameSite=Laxか。OAuth はプロバイダから自分のサイトへリダイレクトで戻ってくる流れがある。Strictすぎると戻り直後の遷移でセッションが送られず不都合が出やすい。ログインセッションはLaxが無難な既定。
8. 【バックエンド】寄せ書き API
Section titled “8. 【バックエンド】寄せ書き API”寄せ書きとアカウントのAPIを Hono で追加して。- GET /api/board: 公開。display_name と message が両方あるユーザーを新しい順に返す。 id・provider・provider_user_id など内部情報は返さない(表示に必要な名前と一言だけ)。- GET /api/me: 要ログイン。自分の display_name と message を返す。- PUT /api/me/post: 要ログイン。名前と一言を受け取り、自分の users 行を更新(1人1投稿の upsert)。- POST /api/logout: セッション削除+Cookie失効。- DELETE /api/me: 要ログイン。自分の users 行を削除する(=アカウント削除)。sessions は ON DELETE CASCADE で一緒に消える。あわせて現在のセッション Cookie も失効させる。寄せ書き(name/message)は users 行に載っているので、削除すると board からも消える。本人の行だけを消し、他人の行は絶対に消さない。9. 【フロントエンド】画面
Section titled “9. 【フロントエンド】画面”public/index.html・app.js・style.css を作る。ログインは各プロバイダのボタンを /api/auth/<プロバイダ> に飛ばすだけ。
public/index.html, app.js, style.css を作って。寄せ書きアプリの画面。- 上部に寄せ書き一覧(GET /api/board)。各投稿は角丸のカード型ボックスにして、横に並べて折り返すグリッドで表示する(画面が広ければ2〜3列、スマホ幅では1列に折り返す)。各カードに名前(強調)と一言を入れる。色は既存のまま、ボックスの形と並びだけカード型にする。ユーザー入力にはXSS防止の処置を入れる。- 未ログイン: 「Google でログイン」ボタン。リンク先は /api/auth/google(プロバイダを増やしたらボタンも足す)。- ログイン済み(GET /api/me が成功): 名前と一言の入力+保存(PUT /api/me/post)、ログアウト(POST /api/logout)、アカウント削除(DELETE /api/me)。- アカウント削除は取り消せないので、実行前に強い確認(例:「本当に削除しますか? あなたの投稿も消えます」)を出す。成功したらログアウト状態(未ログイン表示)に戻す。- ログイン状態は /api/me が 200 か 401 かで判定する。10. デプロイ
Section titled “10. デプロイ”本番の D1 にマイグレーションを適用し、デプロイする。
npx wrangler d1 migrations apply yosegaki-social-db --remotenpx wrangler deploy完了すると https://プロジェクト名.アカウント.workers.dev の形で公開 URL が出る。
順序に注意:本番 URL が決まってから各プロバイダに登録する。デプロイで本番ドメインが確定したら、使う各プロバイダのコンソールに
https://<本番ドメイン>/api/auth/<プロバイダ>をリダイレクト URI として追加する(登録前に試すとredirect_uri_mismatchになる)。
11. 動かす・動作確認(自分でテストする)
Section titled “11. 動かす・動作確認(自分でテストする)”まず「Google でログイン」を押すと、Google のアカウント選択画面に飛ぶ(この記事のアプリ名「みんなの寄せ書き」に移動、と出る)。
アカウントを選ぶ(テストモードでは、弾かれたらそのアカウントをテストユーザーに追加する)
アカウントを選ぶと、Google アカウントで続行する確認が出る。本記事は openid だけしか要求しないので、「名前やメールアドレスを渡す」という確認は出ず、最小限の同意で済む(渡す個人情報が無いぶん画面もあっさりする)。進めるとアプリに戻る。もしここで「アクセスをブロック」と出たら、そのアカウントを Google 側のテストユーザーに追加する(§4-1)。
openid だけなので、渡す個人情報の確認は最小限で進む
アプリに戻ると、ログイン状態になる。お名前欄は空(Google の実名を初期値にしていない。§1-4)。ここに自分の名前を入力する。
ログイン後。お名前は空なので、自分で入力して投稿する
一言を入れて保存すると、寄せ書きに自分のカードが並ぶ。
投稿が寄せ書きに反映される
別の Google アカウントでログインすると、別のユーザーとして扱われる。同じ Google でも、アカウントが違えば provider_user_id(sub)が違うため、別の users 行になり、寄せ書きにもう1枚の別カードが並ぶ。実際に2つ目のアカウントで入って投稿すると、こうなる(手元で確認済み)。
別の Google アカウントは別ユーザー。カードが2枚に増える(連携でまとめるのは §13 の宿題)
続けて、仕様どおり安全に動くかを自分で確かめる(まだ Google だけ)。
| 確認すること | やり方 | 期待 |
|---|---|---|
| 公開閲覧 | シークレットウィンドウでトップを開く | 寄せ書きは見えるが投稿欄は出ない |
| Google でログイン | 「Google でログイン」 | 同意後に戻り、投稿できる |
| 1人1投稿 | もう一度投稿 | カードが増えず上書きされる |
| 未ログインで保護API | シークレットで GET /api/me を直接開く | 401 |
| 一意キーは(provider+id) | 同じ Google で再ログイン | 同じ1行が使われる(users が増えない) |
| ログアウト | ログアウト後に GET /api/me | 401 に戻る |
| アカウント削除 | ログイン後に「アカウント削除」→ 確認 | 自分の行と投稿が消え、board から消える。GET /api/me は 401 に戻る |
| 内部情報を出さない | GET /api/board の応答を見る | 名前と一言だけ(provider・id は無い) |
Google で一通り動いたら、次の節で X などを足す。
users の中身を目視する例。
npx wrangler d1 execute yosegaki-social-db --remote --command "SELECT provider, display_name FROM users"12. プロバイダを増やす(例:X を足す)
Section titled “12. プロバイダを増やす(例:X を足す)”Google が動いたら、他のプロバイダを1つずつ足す。直すのは前述の3箇所だけ。ここでは X(Twitter)を例にする。
(1) X 側の登録(§4):X Developer Portal で OAuth 2.0 を有効にしたアプリを作り、リダイレクト URI に http://localhost:8787/api/auth/x(と本番 URL)を登録。X_ID / X_SECRET を wrangler secret に入れる。
(2) ログインルートと normalize に X を足す(§7-1):
X(Twitter)のログインを足して。- GET /api/auth/x に xAuth を噛ませる。client_id/secret は c.env.X_ID / X_SECRET。 scope は ["tweet.read","users.read"](本人の一意ID が取れれば足りる。offline.access は継続アクセス用なので今回は不要)。- normalize に x の分岐を足す: user-x → providerUserId=id だけを取る(email も表示名も受け取らない。§1-4)。normalize に足すのは X の1分岐(user-x の id を providerUserId にする)だけ。email 非取得の方針は Google と同じなので、X がメールを返さないことも最初から関係ない。
(GitHub を足すなら githubAuth/user-github/id を providerUserId にする。email を取りに行く user:email スコープは付けない。)
(3) ボタンを足す(§9):フロントに「X でログイン」ボタンを1つ加え、リンク先を /api/auth/x にする。
足して(ローカルで試し、再デプロイして)確認する。
| 確認すること | やり方 | 期待 |
|---|---|---|
| X でログイン | 「X でログイン」 | 同意後に戻り、投稿できる |
| 個人情報を取らない | X でログイン後に users を見る | provider_user_id と(本人が入れた)表示名だけ。email 列は無い |
| プロバイダ違いは別人 | Google と X の両方で入る | users が2行できる(設計どおり。連携は宿題) |
同じ要領で GitHub・Facebook なども足せる。差は normalize の分岐とボタンとコンソール登録に閉じる。
13. セキュリティ要点・備忘録
Section titled “13. セキュリティ要点・備忘録”state(CSRF 対策)とトークン交換はライブラリ任せ:@hono/oauth-providersが担保。OAuth の往復を手書きしない。client_secretは Worker のシークレット:wrangler secretに入れ、コード・フロント・リポジトリに置かない。redirect_uriは登録と完全一致:プロバイダごとに、ローカルと本番の両方を登録。- 一意キーは(provider+provider_user_id):
UNIQUE(provider, provider_user_id)。表示名などで同一性を判断しない。 - 個人情報は取らない(データ最小化・§1-4):要求スコープは
openidだけ。email もプロフィールも受け取らず、保存もしない。使わない個人情報は「そもそも取得しない」のが一番安全。 - 表示名は本人入力・実名を初期値にしない:プロバイダが返す実名を勝手に公開しない。
- セッション Cookie:
HttpOnly; Secure; SameSite=Lax。localStorage にセッションを置かない(サーバー発行 Cookie にすることで、Safari の localStorage 自動削除の影響も受けない)。 - 公開データに内部情報を混ぜない:
/api/boardは名前と一言だけ。provider・id(provider_user_id)を返さない。 - ユーザー入力の XSS 対策:名前・一言の表示時に無害化する。
- アカウント削除は本人だけ・取り消し不可:
DELETE /api/meは requireAuth で自分の行だけを削除する。sessions はON DELETE CASCADEで消え、Cookie も失効させる。削除後に同じ Google で入り直すと、別の新規アカウントになる(前の投稿は戻らない)。
本番運用に向けた宿題:
- プロバイダ側の連携解除は別:アプリのアカウントを削除しても、Google などプロバイダ側の「このアプリへのアクセス許可」は残る。完全に切りたいユーザーには、各プロバイダのアカウント設定で連携を解除してもらう(アプリ側からは制御できない)。削除完了画面にその案内を添えるとていねい。
- アカウント連携:同じ人が別プロバイダで入ると別アカウントになる。1人にまとめたいなら、ログイン中に「別のログイン方法を追加」して同じ users 行に紐づける仕組みを足す(本記事では未対応)。
- テストモードから本番への切り替え:Google は「対象」ページが**「テスト中」のあいだ、テストユーザーに登録した人しかログインできず、その上限は 100 人(アプリの全期間で通算)**(→ §4-1(3))。不特定多数に使ってもらうなら「アプリを公開」で本番に切り替える。本記事のように
openidだけを要求する場合、押すとその場で「本番環境」になり、審査は求められなかった(機密スコープを含まないため。手元で確認済み)。X や Facebook などでも同様に、開発者コンソール側の審査・本番切り替えが要ることがある。要求スコープは最小限に(本記事はopenidだけ。必要になってから足す)。 - セッションの掃除と失効:期限切れ
sessionsの定期削除(Cron Triggers など)、ログアウトの確実な失効。 - レート制限:ログイン開始や API に Cloudflare の Rate Limiting を足す。
これで、複数のソーシャルログインによる一般ユーザーのログイン・投稿・ログアウトが、パスワード保管もメール送信もなしに、Cloudflare の無料枠だけで、しかも人数無制限で完成した。プロバイダの差は正規化の1箇所に閉じ込めたので、対応サービスを増やすのも容易になっている。
別の認証方法:ログインを外部サービスに預けたくない場合は、端末の生体認証などでパスワードもアカウント登録も無しにする「パスキー」という方法もある。同じ寄せ書きをパスキーで作る版が パスキーで複数ユーザーのアカウントを作る(PKA)にある。












