メインコンテンツへスキップ
見出し画像

安全にするために持たないことにした

    こんにちは、よなです。

    YONA GAMESという個人ゲームサイトを運営しています。

    YONA GAMES | 無料ブラウザゲーム YONA GAMESは、ブラウザで無料ゲームを公開・提供する個人ゲームスタジオです。第一作「MINECOIN Miner」 yona-games.com

    最初は「ローカルで動けばOK」だと思っていました。ところが、いざ自分のサイトから一般公開しようと決めた瞬間、急に考えることが山積みになりました。

    ログインはどうする。セーブデータはどこに置く。他人のデータが見えてしまわないか。そもそもブラウザゲームってどうやって世に出すものなのか。

    ゲームを作るのと、ゲームを公開するのは、別の競技だったんだなというのが、この時点での率直な感想です。マインスイーパーの盤面より、こっちのほうがよっぽど地雷が多い。

    最初に考えていた認証の形(たぶん多くの人が最初に考えるやつ)

    当初のイメージは、GoogleやGitHubでログインしてもらい、そこで取得したメールアドレスやトークンをそのままユーザー管理に使う、というものでした。ユーザー名も登録してもらう。

    ゲームサイトにユーザーがいて、ユーザーごとにセーブデータがある。シンプルでよさそうに見えます。

    もし自分でゲームを公開するとしたら、あなたもまず「メールアドレスをそのままIDにしよう」と考えませんか?私は考えました。そして、その考えのまま進めていたら、あとで痛い目を見ていたと思います。

    ChatGPTに相談してみると、少し立ち止まったほうがよさそうでした。メールアドレスは変更されることがあります。OAuthのトークンは、ログイン中の本人であることを証明する一時的な通行証にすぎません。どちらも、ゲームデータを一生紐づけるIDには向いていない。

    正直に言うと、OAuthという言葉自体は知っていても、仕組みまではふわっとしか理解していませんでした。「じゃあ、そのトークンも一緒に保存しておけばいいですよね?」と真顔でChatGPTに聞いたら、「いや、それはむしろ危ないです」と返ってきて、地味に気まずかったです。知ってるつもりの言葉ほど、実は仕組みを説明できない。知識のマインスイーパーで、うっかり地雷を踏むところでした。

    そこで、認証とゲーム内のユーザー管理を分けることにしました。

    Google/GitHub
      ↓ 認証だけ担当
    Supabase Auth
      ↓ 内部ユーザーIDを新規発行
    Supabase PostgreSQL
      ↓
    ゲームのセーブデータ
    

    Supabase自体は今回初めて触るサービスでしたが、「PostgreSQL」という単語が出てきた瞬間、急に理解が進みました。本業でPostgreSQLをDBに使った経験があったので、「知らないサービスだけど、中身は知ってるDBなんだ」とわかったら、一気に距離が縮まりました。知らない外国語の中に、知ってる単語が一つ混ざっているだけで、文章全体が読める気がしてくる。あの感覚に近いです。

    ゲーム側で扱うのは、Supabaseが発行する内部のユーザーIDだけ。メールアドレスはゲーム用データベースにコピーしない。OAuthトークンも保存しない。ランキングを作らないなら、ユーザー名すら持たない。

    「便利そうだから全部持っておく」ではなく、「使わないものは最初から持たない」。 冷蔵庫の中身と同じで、賞味期限切れの情報ほど後で厄介です。

    「Reactは信用しない」という地味に刺さった言葉

    セキュリティについて相談していたときに、印象に残った一言があります。

    Reactは信用しない。URLも信用しない。リクエストボディも信用しない。最後はDBのRLSで止める。

    ChatGPT

    これ、言い換えると「フロントエンドは口約束、データベースは契約書」みたいな話です。

    ブラウザ上の画面は、利用者が自由に書き換えられます。開発者ツールを開けばURLも書き換えられるし、画面を経由せずAPIを直接叩くこともできる。画面上に「これはあなたのデータです」と表示していても、それはただの案内表示であって、鍵ではありません。

    なので、Supabase PostgreSQLのRow Level Security(RLS)を最後の防御線にしました。基本条件はこれだけです。

    user_id = auth.uid()
    

    ログイン中の本人のIDと、データに刻まれたIDが一致したときだけ、読み書きを許可する。

    画面側で隠すのではなく、データベース側で止める。 玄関に「ご自由にお入りください」の張り紙をするのではなく、そもそも鍵をかけておく。今回の設計変更の中で、一番効いたのはここでした。

    CloudflareとSupabase、どっちが守ってるのか

    ちなみに、Cloudflareという名前を知ったのも、今回のゲーム制作がきっかけです。2ヶ月前の自分に「今からCloudflareの設定をするよ」と言っても、たぶん「それ、呪文か何か?」という顔をされると思います。まずは「Cloudflareって何するものなの?」を調べるところからのスタートでした。

    CloudflareとSupabase、どちらか一方が全部を守ってくれるわけではありません。むしろ役割はきっちり分業です。

    画像

    あなたのアプリは、「入口を守る係」と「中身を守る係」、ちゃんと分かれていますか?私は最初、なんとなく全部を一つのレイヤーで守れる気がしていました。実際は、玄関の鍵と金庫の鍵は別物でした。

    Cloudflare PagesにはYONA GAMESのサイトを置き、MINECOIN Miner本体は別のCloudflare Workerとして配信する構成にしています。

    https://yona-games.com/minecoin-miner/
    

    サイトとゲームを分けたのは、今後ゲームが増えたときに管理しやすくするためです。

    ここで、地味にやらかしました。

    サイト側を更新しても、ゲーム側のWorkerは自動では更新されなかったんです。 「同じサイトの中にあるんだから、一緒にデプロイされるだろう」と信じて疑っていなかったのですが、実際には別の配信単位でした。看板だけ新しくして、店の中の商品は前のままだった、みたいな話です。お客さんからすると、看板と中身が食い違っている店ほど不安なものはありません。

    この顛末は、次回もう少し詳しく書こうと思います。

    Codexにプラグインがあったのは地味に助かった

    普段の開発はCodex Appで進めているのですが、CloudflareとSupabase、それぞれの拡張機能がすでに用意されていました。おかげで、コマンドの叩き方をゼロから調べなくても、ある程度はAIに任せて設定を進められました。

    ただ、全部が自動で終わったわけではありません。管理画面をブラウザで開いて、ポチポチとボタンを押さなきゃいけない設定も普通に残っていました。「この項目、どこに何を入れればいいの」を一つずつAIに確認しながら、画面とチャットを往復する時間もそれなりにありました。プラグインは近道をくれるけど、迷子防止の地図まではくれない。そこは自分の足で歩く必要がありました。

    防御機能を最初から全部乗せするべきか問題

    CloudflareにはWAF、レート制限、Turnstile、Accessなど、盾がいろいろ用意されています。

    最初は「全部つけたほうが安全でしょ」と思いました。フル装備の騎士のほうが強そうに見えます。

    でも、機能を増やすほど設定項目も増えます。フル装備すぎる騎士は、重すぎて自分の家の玄関からも出られなくなる。 設定を盛りすぎて公開できなくなっては、本末転倒です。

    なので、最初の公開ではこう整理しました。

    • 一般ユーザー:Supabase Auth

    • セーブデータ:Supabase RLS

    • ゲスト開始:Turnstile

    • サイト公開:Cloudflare Pages

    • ゲーム配信:Cloudflare Worker

    • 管理画面:必要になったらCloudflare Access

    Cloudflare Accessをサイト全体にかけると、一般ユーザーまで別の認証を求められてしまいます。それでは本末転倒すぎるので、使うなら管理画面や運営用APIだけに絞ることにしました。

    「守りを固める」と「誰も入れなくする」は、紙一重で全然違うものだと痛感しました。

    一番の学びは「守り方」より先に「持たない」だった

    今回一番大きかった気づきは、安全に保存する方法より先に、そもそも保存しない情報を決めることでした。

    ユーザー名が必要ないなら、登録させない。ゲーム側でメールアドレスを使わないなら、ゲーム用テーブルにコピーしない。GoogleやGitHubのAPIを呼ばないなら、アクセストークンを保存しない。

    一見、機能を削っているだけに見えます。でも実際には、セキュリティ対策の量と実装量、両方が一緒に減りました。空き巣が入っても、盗まれて困るものが家の中にそもそもなければ、被害はゼロです。 守る対象を増やすより、守る対象を減らすほうが、案外コスパがいい。

    もし今、自分のアプリに「なんとなく持っているけど使っていない個人情報」があったら、それは一度洗い出す価値があるかもしれません。

    もちろん、これで完全に安全になったわけではありません。ここまでは、RLSのコード例を作り、構成を決めただけです。実際のSupabase設定、Googleログイン、Cloudflareへのデプロイ、テストプレイは、この後の工程でした。

    設計図はできた。ようやく公開作業に進める、というところです。

    そして次に待っていたのは、ローカルでは一度も出なかった本番ビルドのエラーでした。

    次回予告

    次回は、Cloudflare Pagesへ初めてデプロイしたときに起きた話です。

    build:productionが存在しない。本番環境変数が足りない。ローカルでは動くのに本番だけ失敗する。

    「ローカルでは動きました」が、一番信用できない報告だと学んだ回です。

    ではまた!


    PC専用ブラウザゲーム『MINECOIN Miner』絶賛公開中!
    ソシャゲ風マインスイーパーで鉱石の収集・スキルで素早く盤面攻略・深層探査などのやりこみ要素もあります!ぜひプレイしてみてください。


    YONA GAMES | 無料ブラウザゲーム YONA GAMESは、ブラウザで無料ゲームを公開・提供する個人ゲームスタジオです。第一作「MINECOIN Miner」 yona-games.com


     
     

    よな

     
     
    群馬在住の2児パパ👨‍🦰 日常・エッセイ・AIゲーム制作🎮 個人開発はCodexをメインで使用💻 10代のころ夢に見たゲーム制作を今、AIの力で実現中! 個人ゲームスタジオ【YONA GAMES】を運営 https://yona-games.com/

    あなたへのおすすめ