ハコヅミを作る #27:ステップ2 の方針を決め、Google アカウントでログインできるようにする

前回(#26)で、ステップ1(自分用の機能を充実させる)が終わりました。ここからは、自分以外の人が使うことを考えるステップ2 です。

今回は、作る前に決めたことと、最初の一歩の「ログイン」です。

  • ステップ2 を、どこまでにするか
  • 会員区分(フリー・有料・管理者)を、どう持つか
  • Better Auth で、Google アカウントでログインできるようにする(#133)

ステップ2 を「公開の準備」に絞る

前回の最後に、ステップ2 は「複数ユーザーでの共有」と書きました。ところが、やることを並べてみると、量が多すぎました。ログイン、会員区分、課金、共有、公開範囲……と続きます。

Claude Code に候補を挙げてもらい、相談して、次のように分けました。

段階 中身
ステップ2 一般に公開するために必要なもの(ログイン、管理者、上限、アカウントの画面、退会、エクスポート、規約など)
ステップ3 共有(キャンプ履歴やおすすめ情報をほかの人に見せる、公開範囲)
課金 有料会員・休止。公開のあとでもよい

共有は将来やりたい機能ですが、最初の公開には要りません。「共有を見すえて、今やっておかないと困るもの」だけをステップ2 に入れることにしました。聞いてみると、それは2つだけでした。

  • 表示名:Google の名前(本名のことが多い)とは別に持つ
  • 規約の書き方:「登録した内容は、初めは自分だけが見られる。公開は、本人が選んだものに限る」と書いておく

公開範囲の列や、ほかの人のデータを読む仕組みは、共有を作るときに足せば間に合います。


会員区分は、3つの軸に分ける

将来は「フリー会員」「有料会員」「管理者」を作りたいと考えています。冬はキャンプに行かないので、有料会員が休める「休止」も欲しいところです。

最初は、これを1つの区分に並べるつもりでした。Claude Code の案は、3つの軸に分けるものでした。

軸 値 誰が変えるか
権限 一般/管理者 自分(運営)だけ
プラン フリー/有料 本人が申し込む・解約する
契約の状態 有効/休止/支払い遅れ/解約済み 本人と Stripe

1つに並べると、「管理者が有料の機能を試す」「有料会員が冬だけ休む」といった組み合わせで困ります。軸を分けておけば、画面やサーバーが見るのは、そこから決まる「今の扱い」だけで済みます。

もう1つ、先に決めたことがあります。上限を超えたデータは消さない、です。有料からフリーに戻って上限を超えていても、見る・チェックする・編集するはできます。止めるのは、新しく足すことだけです。休止や解約が怖くなければ、戻ってきやすいと考えました。


ログインは、最初から Better Auth にする

ハコヅミは、ここまで Cloudflare Access(ログインした人だけが開ける仕組み)の後ろで、自分1人で使ってきました。全部のテーブルの user_id には、固定の値 owner を入れています。

知り合いだけに使ってもらうなら、Access で招待する手もありました。ただ、それでは利用者ごとにデータを分けられません。遠回りせず、最初から Better Auth を入れることにしました。ログインの方法は、Google アカウントだけです。パスワードを預からずに済みます。

user_id は、メールアドレスにしない

Issue の下書きを読んで、1つ確かめました。user_id は、メールアドレスではなく、ランダムな値になるのか、という点です。

答えは、Better Auth が作るランダムな値でした。メールアドレスを id にすると、アドレスを変えたときに全部のデータを付け替えることになります。将来、共有の URL に id が出ても、メールアドレスは漏れません。

今のデータは、いったん消す

owner のデータを、新しい自分の id に付け替えるマイグレーションも考えました。ただ、id が決まるのは、初めてログインしたあとです。マイグレーションの時点では分かりません。

今のデータは、Notion から取り込んだものに、試しに足したサンプルです。消してしまって、ログインのあとに Notion から取り込み直すことにしました。取り込みのスクリプトは、#7で作ったものです。「全員の分を消して入れ直す」作りだったので、渡したユーザーの分だけを入れ直す形に直しています。


人が先に済ませておくこと

実装の前に、人がダッシュボードで済ませることが2つありました。

  1. Google Cloud で、OAuth のクライアントを作る(開発用と本番用)
  2. Worker の Secrets に、3つの値を入れる(Better Auth の秘密、Google のクライアント ID とシークレット)

秘密の値は、チャットには貼らないことにしています。手順だけを Claude Code に聞いて、値は自分でダッシュボードに入れました。手元で動かすときの値は .dev.vars に書き、Git の対象から外しています。


実装で気をつけたこと

ログインの画面。ヘッダーにはロゴとアプリ名だけが出ていて、メニューはない。見出し「ログイン」の下のカードに「Google でログイン」のボタンが1つある

ログインの画面。ログインしていないときは、ヘッダーのメニューを出さない

「誰がログインしているか」を決める場所は、1か所

今までも、user_id を決める場所は src/hooks.server.ts だけにしていました。そこを、固定の値から Better Auth のセッションに差し替えています。ログインしていなければ、ログインの画面と Better Auth の処理のほかは、すべてログインの画面に移します。

ページの側は、今までどおり locals.userId を使うだけです。ページのコードは、ほとんど変えずに済みました。

戻る先は、アプリの中だけ

ログインの画面に移すときは、もとの URL を redirectTo に付けて、ログインのあとに戻れるようにしています。ここは、悪用されやすいところです。//example.com のような値を受け付けると、ログインのあとに別のサイトへ飛ばされます。

戻る先として受け付けるのは、アプリの中のパスだけにしました。判定は関数に切り出して、ユニットテストしています。

セッションは30日

ハコヅミを使うのは、車に荷物を積んでいるときです。そこでログインし直しになると困ります。セッションは30日にして、使うたびに延びるようにしました。


E2E は、Google に行かない

E2E テストで、本物の Google のログインは通せません。そこで、テスト用のユーザーとセッションを、テストの前にローカルの D1 に入れておきます。Better Auth と同じ形で署名したクッキーを、全部のテストのブラウザに付けます。

つまずいた点が2つありました。

Google へ移るところを、止められない

「Google でログイン」を押すと、Google の画面へ移ります。テストでは、移る先の URL だけを確かめたいところです。Playwright の page.route() で止めようとしましたが、サーバーからのリダイレクトは止まりませんでした。

結局、フォームを直接送り、リダイレクトを追わずに、応答の location を確かめる形にしています。

秘密の値を探すテストが、自分に引っかかる

「秘密の値がリポジトリに入っていない」ことを確かめるテストも書きました。Google のクライアント シークレットの形の文字を、全部のファイルから探します。

ところが、テストのコメントに、探している文字の例を書いていました。テストのファイルをコミットしたとたんに、自分自身に引っかかって落ちます。コメントを直して通しました。


取り込み直しと、本番での失敗

マージして、開発環境でログインし、Notion から取り込み直しました。ここでも、細かくつまずいています。

  • ファイル名の空白:Notion から書き出した CSV は、名前に空白が入っています。引用符で囲まずにコマンドに渡して、「ファイルがない」と言われました
  • コンソールに貼るもの:SQL をクリップボードに入れる pbcopy のコマンドごと、D1 のコンソールに貼ってしまいました(エラーになるだけで、害はありません)。#7でも、同じような間違いをしています

取り込みのあと、開発用の D1 を見ると、ギア 56 件・コンテナ 6 件が、自分の新しい id で入っていました。

本番でログインできない

本番リリースのあと、本番でログインすると、「ログインできませんでした」と出ました。URL には error=invalid_code と付いています。

Google から戻ってきたあと、サーバーが Google に「このコードを、トークンに換えてほしい」と頼むところで、断られていました。原因は、本番の Worker に入れた、Google のクライアント シークレットの入れ間違いです。入れ直すと、ログインできました。

この失敗で、画面に出る理由が「ログインできませんでした」だけでは、何が起きたか分からないことにも気づきました。あとで直す候補に入れてあります。


おわりに

  • 段階の名前より、「何ができたら次に進むか」で区切る。ステップ2 は「一般に公開できる」まで
  • 区分は、変える人が違うものを別の軸にする
  • user_id は、ランダムな値にする。メールアドレスは変わるし、漏らしたくない
  • 「誰がログインしているか」を決める場所が1か所なら、ログインの仕組みを入れ替えても、ページは変えずに済む
  • E2E のログインは、本物を通さず、署名したクッキーを付ける

次回は、1回に10分近くかかるようになっていた E2E を、速くした話です。

← トップページに戻る