連載「クリシンクエストをつくる」· 第5編/全8編
ログインは、つくらない・混ぜない — 入口を3つに分けた話
パスワードは自分で抱えない。身分証はJSから触れない場所に。そして立場ごとに入口を分ける
ログインは地味ですが、いちばん事故が怖いところでもあります。クリシンクエストは自前の認証をやめて専門サービスに預け、トークンの持ち方を工夫し、さらに学習者・企業担当者・運営者で入口(URL)を分けました。なぜ「つくらない・混ぜない」が安全につながるのかを、開発の裏側からやさしくお話しします。
連載「クリシンクエストをつくる」· 第5編/全8編
パスワードは自分で抱えない。身分証はJSから触れない場所に。そして立場ごとに入口を分ける
ログインは地味ですが、いちばん事故が怖いところでもあります。クリシンクエストは自前の認証をやめて専門サービスに預け、トークンの持ち方を工夫し、さらに学習者・企業担当者・運営者で入口(URL)を分けました。なぜ「つくらない・混ぜない」が安全につながるのかを、開発の裏側からやさしくお話しします。
前々回、本番環境をつくった話の最後に「認証(ログイン)は自前でつくらない」と書きました。今回はその続きです。ログインまわりを、どう設計したか。その裏側を、一緒にのぞいていきましょう。
ログインの画面って、見た目はただの「メールとパスワードの入力欄」ですよね。とても地味です。でも、ここはアプリの中でいちばん事故が怖い場所でもあるんです。漏れたときの被害が大きく、しかも「正しくやる」のが想像以上に難しい。今回の設計を、ひとことで言うなら「つくらない・混ぜない」。この2つに尽きます。なぜこの2つなのかを、これからゆっくりお話ししますね。
まずは「つくらない」から。
パスワードを自前で管理するのは、本当に難しいことなんです。そのまま保存してはいけない(必ず元に戻せない形に変換して保管する)、漏洩や総当たり攻撃に備える、リセットの導線を用意する……。考えることが、いくつもあります。どれも一つ間違えれば、利用者全員に迷惑がかかってしまう。だからこそ、自分で抱えるほど危ないものは、専門家に預ける。これが今回の基本方針でした。
そこで、認証は専門のサービス(Supabase の認証機能)に任せることにしました。仕組みは、こんなふうになっています。
肝心のパスワードの管理は、まるごと専門サービスに委ねています。ログインを担うアプリ側の台帳には、パスワードを置かない方針です。考えてみると、当たり前のようでいて大事なことなんですよね。手元に無いものは、漏らしようがない。いちばん危険なものを自分で抱えないこと自体が、実はとても強力な対策になるのです。
実は移行する前は、自分でパスワードを保管し、ログインのしくみも手作りしていました。それを専門サービスに切り替えるとき、利用者情報の「台帳」を作り直すことになったんです。
専門サービスは、利用者一人ひとりに固有の番号(ID)を割り当ててくれます。そこで、アプリ側にもその番号をカギにした台帳(プロフィール)を用意して、表示名や所属組織といった、アプリ側で必要な情報をそこに持つことにしました。本人確認とパスワードは専門サービスに、アプリの中身はアプリに。こうして役割をきれいに分けたわけです。古い手作りのしくみは、安全な土台にそっと載せ替えました。引っ越しは少し手間でしたが、後から振り返ると、やっておいてよかったと思える作業でした。
ここで、少し反省を書かせてください。この引っ越し作業、そもそも要らなかったのではないか——いま振り返ると、そう思うんです。
クリシンクエストは、まず「とりあえず動くもの(PoC)」を、自分のパソコンの中だけでつくりはじめました。ところが、その試作の段階で、つい欲が出てしまったんですね。ログインのしくみまで自分で手作りし、データの置き場所も「自分のパソコンで動くこと」を前提に組んでいきました。試して確かめるだけのはずが、いつのまにか本番のように作り込んでいた、というわけです。
そのツケが、あとからまとめて返ってきました。本番はクラウド(Vercel と Supabase)に出すと決めた瞬間、手作りのログインは専門サービスへ引っ越すことになり、前回お話しした「距離」の問題——アプリとデータベースが遠く離れて遅くなる、あの一件も顔を出しました。どちらも、試作のときに置いた前提が、本番の前提と食い違っていたために生まれた作業です。
では、どうすればよかったのか。いま思うのは、二つです。ひとつは、PoC は「ログインなしでもプレイできる」ところまでで、ぐっとこらえて止めておくこと。確かめたかったのは「このゲームは面白いか」であって、「ログインを正しくつくれるか」ではなかったはずなんです。もうひとつは、本番(MVP)へ進むと決める前に、「クラウド(Vercel+Supabase)で動かす」という前提を置いた計画書を、先に一枚用意しておくこと。
設計の段階でその流れさえ見えていれば、PoC のつくり方そのものが変わっていたはずです。最初から「いずれ専門サービスに認証を預ける」「いずれクラウドにデータを置く」と分かっていれば、手作りのログインに時間をかけることも、ローカル前提でデータの置き場所を固めてしまうこともなかった。何を「まだつくらない」かを先に決めておくことは、何をつくるかを決めるのと同じくらい大事だった——これが、今回いちばん身にしみた学びです。あなたが何かを試作するときも、よかったら一度立ち止まってみてください。それは本当に、いま確かめたいことのために必要な作り込みでしょうか。
預けたとはいえ、丸投げではありません。届いた身分証(トークン)が本物で、有効期限内かどうかは、アプリ側できちんと検証しています。
ここは、追加の重たい部品を入れず、ブラウザやサーバーに最初から備わっている暗号の仕組みだけで確かめるようにしました。専門サービスが公開している「照合用のカギ」と突き合わせて、偽造や改ざんがないかをチェックする。任せるところは任せ、確かめるところは自分で確かめる。この線引きが、とても大事だったと感じています。依存する部品が少ないほど、壊れる場所も、更新に追われる場所も減りますからね。
本物だと確認できた身分証は、次回以降のためにブラウザに預けておきます。ここで問題になるのが、「どこに、どう置くか」です。地味に思えて、実はここが分かれ道なんです。
クリシンクエストでは、これを JavaScript からは読めない形の Cookie に入れています。こうしておくと、たとえ悪意のあるスクリプトがページに紛れ込んでも、その身分証を盗み出せません。逆に、手軽な保管場所(JavaScript から自由に読める置き場)に入れてしまうと、何かの拍子に抜き取られる危険があるんですね。いちばん大事なものほど、プログラムからすら触れにくい場所に置いておく。これも「持ち方」で守るという発想です。
身分証には、わざと短い有効期限をつけてあります。万一盗まれても、すぐ使えなくなるようにするためです。
とはいえ、数十分ごとにログインし直してください、と言われたら、使う人にとっては苦痛でしかありませんよね。みなさんも、何度もパスワードを聞かれて、うんざりした経験はありませんか。そこで、期限が切れたら裏側で静かに新しい身分証に更新するしくみを入れました。利用者は何も意識しないまま、ログインがそのまま続きます。安全(短い期限)と快適さ(切れても止まらない)は、ふつうは綱引きになってしまうもの。その両立を、自動更新にそっと受け持たせました。
ここから「混ぜない」のお話です。
クリシンクエストには、立場の違う3種類の利用者がいます。
最初は、全員を同じログイン画面に通していました。でも、これがあまり良くなかったんです。学習者向けの遊び心のある画面に企業の担当者を通すのは、法人としての受けが悪い。かといって、全員に管理機能の気配を見せるのも、セキュリティ上よろしくありません。やってみて初めて、ここはきちんと分けるべきだと気づきました。
そこで、入口(URL)そのものを3つに分けることにしました。学習者用、企業担当者用、運営者用。見た目も、ログインしたあとの行き先も、立場ごとに変えてあります。学習者はゲームへ、企業担当者は管理画面へ、運営者は運営画面へ。同じ「ログイン」でも、まったく別の扉を3つ用意したわけです。
入口を分けるのは、デザインの都合だけではありません。役割の境界を、構造としてはっきりさせるためでもあります。
ここで大事にしたのは、これらをボタンを隠すといった見た目の細工ではなく、サーバー側の判定で守るということです。画面からボタンを消しても、裏の通信を直接叩かれてしまったら、意味がありませんよね。だから「できる・できない」は、必ずサーバーが最終判断するようにしました。第2編で書いた「答えはブラウザに渡さない」と、考え方はまったく同じです。境界は、性善説ではなく構造で引く。ここでも、その感覚が効いていました。
もうひとつ、地味ですが大切にした点があります。
クリシンクエストは、ログインしなくても“お試し”でプレイできます。その間のプレイ記録は、いったん「名前のない仮の ID」に紐づけて持っているんですね。そして、あとからログインした瞬間に、その仮の記録を本人のアカウントへそっと引き継ぐようにしました。「せっかくプレイしたのに、ログインしたら記録が消えていた」。みなさんも、そんな小さながっかりを味わったことはありませんか。そのがっかりを、なくしたかったんです。安全の設計が、体験のやさしさと地続きであってほしい。そう思っています。
ログインの設計でやったことは、結局のところ、この二つに尽きます。
派手な暗号技術を駆使したわけではありません。むしろ「自分でやらないことを決める」「ごちゃ混ぜにしないことを決める」という、引き算と整理の作業でした。安全は、足し算よりも引き算でつくられることが多い。これは認証に限らず、いろんな場面で効いてくる感覚だと感じています。
検証や実装そのもの、トークンを確かめるコードも、入口ごとの画面も、書いたのは AI です。でも、「何を自分で持たず、何を専門サービスに預けるか」「入口をどう分けるか」という判断だけは、AI に丸投げできませんでした。むしろ、してはいけないと思っています。実装は任せられても、判断は預けられない。とくにセキュリティでは、AI の出力をそのまま信じず、一段疑ってかかるのがディレクターの責任だと考えています。
さて、これで土台と中身はひととおりそろいました。次回は、世に出す前の最後の仕上げ。「ダサい」を、レイアウトから直す。デザインと UX の作り直しについてお話しします。
— クリシンクエスト開発記・第5回