認証基盤の移管で全員がログインできなくなる

認証基盤の移管で全員がログインできなくなる
認証基盤の移管は「データを運ぶ」問題ではなく「ユーザーの同一性を運ぶ」問題であり、ユーザーテーブルを完全に移してもハッシュ方式・ペッパー・ソーシャルログインのsubを同じ形で検証できなければ全員がログイン不能になる。救済策の全員パスワードリセットは休眠ユーザーを大量に失いMAUの前提を壊すため、買い手はハッシュ方式・ソーシャルログインの内訳・移行の実演を事前に確認する必要がある。

登録ユーザー10万人、月次のアクティブが2万、継続率も良好。事業の数字はどこを切っても健全で、買い手側のエンジニアがコードを読んでも大きな地雷は出てこない。それなのに、移管作業を始めた翌週から、サポート窓口が「ログインできません」で埋まっていく ── 認証基盤の引き継ぎで起きる事故は、だいたいこの順序でやってきます。

厄介なのは、この事故が「データを失った」わけではないことです。ユーザーのレコードは1件も欠けていません。メールアドレスも、登録日も、購入履歴も、全部きれいに移っている。移行元と移行先で行数を突き合わせれば、ぴったり一致する。それでもログインできない。

認証基盤の移管が難しいのは、これが「データを運ぶ」問題ではなく、「ユーザーの同一性を運ぶ」問題だからです。この記事では、その同一性がどこで切れるのか、切れたときに事業の数字に何が起きるのか、買い手はどこを見れば事前に気づけるのかを整理していきます。

ユーザーテーブルは運べる、ユーザーは運べない

ユーザーテーブルは運べる、ユーザーは運べない

まず、認証というものが何をしているのかを身も蓋もない形で確認しておきます。ログインとは、「この人は前回来た人と同じ人か」を判定する処理です。パスワード認証なら、保存されたハッシュと、入力されたパスワードから計算し直したハッシュを比べる。ソーシャルログインなら、保存された識別子と、プロバイダから返ってきた識別子を比べる。突き詰めればそれだけです。

ここで重要なのは、保存されている側は「答え」ではなく「答え合わせの道具」でしかないということです。パスワードハッシュはパスワードではありません。一方向に潰した残骸であって、そこから元のパスワードを取り出すことはできない(DBが漏れてもパスワードが漏れないための仕組みなのだから、取り出せないのは当たり前です)。

この「取り出せない」という性質が、移管のときに牙を剥きます。ユーザーテーブルをエクスポートすればハッシュの文字列は取れますし、移行先のDBに入れることもできる。データとしては完璧に運べています。問題は、移行先がそのハッシュを「同じ答え合わせの道具」として使えるかどうかであり、これはまったく別の話です。そして前者だけを確認して「移管完了」と判断してしまう事故が、実際に起きています。

なお、認証を自前で持たず外部の認証サービスやBaaSに預けている事業でも事情は変わりません。Supabaseの移管のようにプロジェクトごと所有権を移せるなら同一性は保たれますが、「新しいプロジェクトを作って中身を移す」方式を選んだ瞬間、話はこれから書くとおりになります。

パスワードハッシュは「同じ形で検証できるか」がすべて

パスワードハッシュは「同じ形で検証できるか」がすべて

では、移行先がハッシュを検証できる条件とは何か。分解しておきます。

アルゴリズムとパラメータが再現できること

bcrypt、scrypt、Argon2、PBKDF2 ── パスワードハッシュのアルゴリズムは複数あり、互いに互換性がありません。移行先が同じアルゴリズムに対応していればハッシュをそのまま持ち込めますが、対応していなければ、その瞬間にハッシュはただの意味のない文字列に変わります。

パラメータも同じです。bcryptのコスト、Argon2のメモリ量・並列度・イテレーション回数。これらはハッシュ文字列の中に埋め込まれる形式($2y$12$ のように先頭にアルゴリズムとコストが乗る)が一般的で、ユーザーごとのランダム値であるソルトも、現代的なアルゴリズムは同じ文字列に同梱します。つまり文字列が、自分の作り方を自分で説明してくれている状態です。

問題は独自実装です。パラメータがコード側の定数にしかない、ソルトが別カラムに置かれている、といった構成があり得る。この場合、コードを読まないと検証を再現できません。ハッシュ列だけを移してソルト列を取りこぼせば、全員分が検証不能になります。

そして、ペッパー

ここが最も見落とされる一点です。ソルトに加えて、アプリケーション側が持つ固定の秘密値をパスワードに混ぜてからハッシュ化する実装があります。ペッパーと呼ばれるもので、設計としては真っ当。むしろセキュリティ意識の高い開発者ほど採用しています。

ただ、ペッパーはDBに入っていません。環境変数か、設定ファイルか、最悪の場合はソースコードの中に定数として書かれています。つまり、ユーザーテーブルを1バイトの欠落もなく完璧に移したところで、ペッパーの値を知らなければ誰一人としてログインできません。しかもこれは作業中に気づきにくい。ハッシュは移っている、アルゴリズムも合っている、ソルトも運んだ。それなのに照合が全部失敗する。原因がDBの外にあるので、DBをいくら眺めても答えが出ません。

方式を丸ごと運べた場合にできること

逆に、方式をそのまま引き継げた場合、移行はかなり穏やかに進みます。移行先で旧方式の検証を実装しておき、ユーザーがログインに成功した瞬間に、そのとき手元にある平文のパスワードで新方式へ再ハッシュして静かに置き換える。ユーザーは何も知らないまま、ログインするたびに勝手に新方式へ移っていきます。ログインしてこない人は旧方式のまま残るのでどこかで線を引く判断は要りますが、全員に一斉に何かを強いるより桁違いにマシです。

全員パスワードリセットは、移行手段ではなく休眠ユーザーへの解約通知

全員パスワードリセットは、移行手段ではなく休眠ユーザーへの解約通知

方式を引き継げなかった場合、教科書的な解決策として出てくるのが「全ユーザーにパスワードの再設定を求める」です。技術的には正しい。ハッシュが検証できないなら設定し直してもらえばいいのだから、筋は通っています。ただ、これを事業の数字の側から見ると、まったく違う景色になります。

アクティブユーザーは戻ってくる。それ以外は戻ってこない

毎日使っているユーザーは、案内が来れば面倒がりながらも再設定します。週に一度のユーザーもだいたい戻ってきます。問題はその外側です。3ヶ月ぶりにふと思い出して開くユーザー。年に一度だけ使うユーザー。サブスクを契約したまま存在を忘れているユーザー ── この層は、案内を受け取った瞬間に「まあ、いいか」を選びます。ログインという手間が発生した時点で、その事業は彼らの生活から静かに外れます。怒って離れるのではありません。ただ、思い出さなくなるだけです。

そして、この層こそが、買い手が見ていた登録ユーザー数の分母を構成しています。「登録10万人」という数字は、その10万人がログインできる状態にあることを前提に成立している。その前提を、よりによって移管作業で自ら壊すのが、全員リセットという選択の正体です。

そもそも案内が届かない

さらに手前の問題があります。移管後の環境は、送信元のIPもドメインの設定も新しくなっていることが多い。そこへ数万件の一斉送信を、しかも「パスワードを再設定してください」という文面で撃つわけです。SPF・DKIM・DMARCの設定が移管の過程で欠けていれば、その大量送信はまとめて迷惑メールに落ちます。設定が正しくても、送信実績のないドメインからの突然の大量送信は警戒される対象です。

加えて、文面そのものがフィッシングと見分けがつきません(セキュリティ意識の高いユーザーほど無視します。無視するのが正解だからです)。リセットを完遂できるのは、案内が届き、迷惑メールに落ちず、フィッシングだと思われず、その上で再設定の手間をかけてくれたユーザーだけです。この漏斗を4段通過する人数は、事前に予測できません。

戻ってきた人数が、そのまま事業の実力になる

リセット完了率は、そのまま新しいアクティブユーザー数の上限になります。ここで露呈するのは、多くの場合「思っていたより、実際に使っている人は少なかった」という事実です。ただ、買い手が支払った金額は、リセット前の数字を前提に決まっている。移管作業が、買収の前提そのものを事後的に否定するという、なかなか救いのない構図です。しかも、失ったユーザーを取り返す方法はありません。もう一度メールを送ったところで、無視した人はもう一度無視するだけです。

ソーシャルログインは「同じ人」が別人として現れる

ソーシャルログインは「同じ人」が別人として現れる

パスワードの話がまだ易しい方だと言うと驚かれるかもしれませんが、本当に厄介なのはソーシャルログインです。「Googleでログイン」「Appleでサインイン」を実装している事業では、ユーザーのパスワードはそもそも自社に存在しません。持っているのは、プロバイダから発行された識別子だけ。この識別子が、移管で変わり得ます。

識別子は「誰が聞いたか」に依存する

ソーシャルログインの土台になっている仕組み(OpenID Connect)では、ユーザーの識別子は sub と呼ばれます。この sub が誰にとっても同じ値かというと、そうとは限りません。プロバイダによっては、sub は「どのアプリが問い合わせたか」に紐づいて発行されます。同じユーザーでも、アプリAが受け取る sub とアプリBが受け取る sub は別の値になる、という設計です。アプリをまたいだ追跡を防ぐための正しい設計なのですが、M&Aの文脈では、この正しさが牙になります。

移管に伴ってOAuthのクライアント(アプリ登録)を買い手側のアカウントで作り直した場合、その新しいクライアントには新しい sub が返ってきます。DBに入っている sub とは一致しません。同じ人が、システムから見ると初めて来た人になります。

その結果、ユーザーがいつもどおり「Googleでログイン」を押すと、購入履歴も設定も何もない、まっさらな新規アカウントが現れます。本人からすれば、自分のデータが消えたようにしか見えません。エラーすら出ないのが、この事故のたちの悪いところです。システムは正常に動いています。ただ、目の前の人を別人だと思っているだけです。

プロバイダごとにルールが違う

実務を難しくしているのは、扱いがプロバイダごとに異なることです。sub がアカウント単位で安定していてクライアントを変えても同じ値が返るプロバイダもあれば、アプリ単位・開発チーム単位でスコープされていて、移管すると値が変わるプロバイダもあります。

後者については、「旧識別子と新識別子を対応づけるための仕組み」が公式に用意されている場合もあります。ただしこれは、移管手続きの中で正しい順序で実施しないと使えません。順序を知らないまま移管を完了させると、後から取り返しがつかない類のものです(そして、その順序はたいてい移管が終わってから調べることになります)。

したがって、DDで確認すべきなのは「ソーシャルログインを使っているか」ではありません。「どのプロバイダを、それぞれ何人が使っているか」です。プロバイダごとに移管の難易度が違う以上、内訳が分からなければリスクの大きさが見積もれない。そして実際のところ、この内訳を即答できる事業は多くありません。「ソーシャルログインもあります」で会話が終わってしまいます。

ちなみに、sub ではなくメールアドレスでユーザーを同定している実装は、一見すると移管に強そうに見えて(アドレスは移管しても変わらないので)、実はもっと悪い。ユーザーがプロバイダ側でアドレスを変更すれば同一性は切れますし、匿名の中継アドレスを発行するプロバイダなら、そのアドレス自体がアプリ単位で発行されるため、結局アプリを作り直せば別のアドレスになります。移管の話を始めて初めて、自社の同一性の土台が何だったかを知る。けっこうよくある展開です。

MFAとセッションは、移管の翌朝に静かに落ちる

MFAとセッションは、移管の翌朝に静かに落ちる

TOTPのシークレットは暗号化されている

二要素認証で認証アプリに6桁の数字が表示されるあれ(TOTP)は、ユーザーごとの秘密の値をサーバーとアプリの双方が持ち、そこから時刻に応じた数字を計算する仕組みです。この秘密の値は、まともな実装ならDBに平文では入っておらず、暗号化されて保存されています。暗号化されているということは、復号する鍵がどこかにあるということで、その鍵はペッパーと同じくDBの外にあります。

この鍵を運べなければ、全ユーザーのMFAシークレットは復号できないただの文字列になります。取れる手は、全員のMFAをリセットして再登録してもらうことです。パスワードリセットと同じ構図ですが、こちらはもっと悪い。MFAをわざわざ設定しているユーザーは、その事業に対して真剣な層 ── つまり有料ユーザーや管理者である可能性が高いからです。いちばん失いたくない層に、いちばん面倒な作業を強いることになります。しかも一斉に外すということは、その期間だけ全アカウントが一要素で守られるということでもある。監視が手薄な移管の時期に、防御を自ら一段下げることになります。

パスキーとセッションの署名鍵

近年増えているパスキー(WebAuthn)はさらに直接的で、資格情報がドメインに紐づいています。ドメインが変われば使えません。仕様としてそう決まっているので、議論の余地も回避策もない。移管を機にドメインを変える計画があるなら、パスキーは全員分が作り直しです。

セッションも同じ構図です。ログイン状態を保持しているトークンやセッションIDは、サーバー側の秘密鍵で署名・暗号化されています。移管でこの鍵が変われば、全ユーザーが強制的にログアウトされる。これ自体は事故ではありません。ログインし直せば済む話です。

ただ、ここまで読んだ方はもうお分かりのとおり、「ログインし直せば済む」が成立するのは、ログインできる場合に限られます。パスワードハッシュが検証できず、ソーシャルログインの同一性も切れている状態で、全員が強制ログアウトされる。移管の翌朝、それまでログイン状態のまま普通に使えていたユーザーが、一斉に締め出された事実に気づく。事故が可視化されるのは、たいていこの瞬間です。

質問ではなく、実演で確かめる

質問ではなく、実演で確かめる

技術的DDの中でも、認証基盤は優先度を上げるべき項目です。理由は単純で、ここが不合格だと、他の項目の調査結果がほとんど意味を失うからです。継続率もLTVもMAUも、ユーザーがログインできる前提の上に乗っている数字なのだから。

1. ハッシュの実物を1行だけ見せてもらう

「パスワードはどう保存していますか」という質問には、ほぼ確実に「ハッシュ化しています」という答えが返ってきます。この答えには情報量がゼロです。必要なのは実物で、パスワード列の値を1行そのまま見せてもらえば多くのことが分かります。$2y$10$ で始まっていれば bcrypt でコストは10。$argon2id$v=19$m= と続けば Argon2id。この形式なら自己記述的なので、移行先が対応してさえいれば運べます。逆に、意味の読み取れない16進の羅列だけなら独自実装の可能性が高く、その瞬間から、コードを読まないと再現できない領域に入ります。判別は1行あれば30秒です(拍子抜けするほど地味な作業ですが、地味さと重要度はまったく比例しません)。

2. DBの外にある秘密の在り処を聞く

ペッパー、MFAシークレットの暗号鍵、セッションの署名鍵。これらがどこにあるかを、「環境変数です」ではなく具体的な場所として答えてもらい、そのうえで「引き渡しの対象に入っていますか」まで踏み込みます。引き渡し物の一覧に載っていない秘密は、引き渡されません。

ここで、コミット履歴に残る秘密が見つかることもあります。ペッパーがソースコードに直書きされていれば移管はむしろ容易になりますが、それはその秘密がリポジトリを見られる全員に共有されていたという意味でもある。移管の容易さとセキュリティの脆さが同居する、皮肉な状態です。

3. ソーシャルログインの内訳を数字で出してもらう

「どのプロバイダを何人が使っているか」を、DBのクエリ1本で出してもらいます。ついでに、そのユーザーがパスワードも設定しているのか(=最悪の場合、パスワードログインへ退避できるのか)も確認しておくと、代替経路の有無が分かります。ソーシャルログインしか経路がないユーザーは、同一性が切れた瞬間に打つ手がなくなります。

4. そして、移行を実演してもらう

ここが本丸です。以上の3つを確認しても、まだ「たぶん運べる」の域を出ません。検証環境に本番のユーザーデータのコピーを流し込み、実在するアカウントで実際にログインできることを、目の前で見せてもらってください。パスワード認証で1件、ソーシャルログインは各プロバイダ1件ずつ、MFA設定済みで1件。通れば、方式は運べています。通らなければ、理由が判明するまで移管の日程を決めてはいけません。

ちなみにこの作業は、それ自体が別のDDを兼ねます。本番のスキーマが手元の定義と一致していなければ、そもそもコピーが流れません。マイグレーション履歴が残っていない事業では、認証の検証をしようとして、その手前のDBの整合性で止まることがあります。それはそれで、極めて重要な発見です。

査定への織り込み方

  • 方式が自己記述的で、DB外の秘密も引き渡し可能、ソーシャルログインの内訳も把握済み: 移行は作業であってリスクではありません。この論点で減点する理由はない
  • 方式は運べるが、ソーシャルログインの一部で同一性が切れる: 切れるプロバイダのユーザー数だけ、失う可能性のある層として見積もります。移管の順序を契約で固定し、完了の確認を支払いの条件に紐づけるのが筋です
  • 方式を運べず、全員リセットが前提: これは既存ユーザーの買収ではありません。リセット完了率を見込んだ数字で評価する話になり、その率は誰にも事前に分かりません。分からないものを買う値段は、分からないなりの値段になります

運べるのはデータで、運びたいのは同一性

運べるのはデータで、運びたいのは同一性

認証基盤の移管が事故になりやすいのは、この作業が「データ移行」という顔をしてやってくるからだと思います。テーブルがあり、行数が数えられ、移した後に突き合わせれば一致する。作業として完結しているように見えてしまう。でも、本当に運びたかったのはデータではありません。「この人は前回来た人と同じ人だ」と言い切れる状態です。ハッシュも、sub も、MFAのシークレットも、それを言い切るための道具にすぎず、道具が機能しなければ、行数がいくら一致していても意味がない。10万行のユーザーテーブルと、10万人のユーザーは、別物です。そしてこの同一性は、継続率もLTVもMAUも、事業のあらゆる数字の下敷きになっている。普段あまりに当たり前すぎて誰も資産として数えていませんが、移管して初めて、それが資産だったと分かります。しかも、分かるのはたいてい失った後です。

売り手の側から言えば、これは自分の事業を正当に評価してもらうための話でもあります。10万人を何年もかけて集めたのなら、その10万人が10万人のまま次のオーナーへ渡ることを、証明できた方がいい。ハッシュ方式を1行見せて、ソーシャルログインの内訳をクエリ1本で出して、検証環境で実際にログインしてみせる。それだけで、「この事業は本当に引き継げる」という一点が、交渉のテーブルに証拠として乗ります。数字を主張するより、よほど効きます。

ユーザーが黙って戻ってきてくれること。それは、事業を続けてきた人にとって最大の成果です。何年もかけて、その人たちが帰ってくる場所を作った。その成果を、移管という事務作業で取りこぼすのは、あまりにもったいない。守るべきなのは、テーブルの行数ではなく、そのユーザー一人ひとりが「自分の場所に帰ってこられる」という事実の方です。

この記事の著者

黒沢 大輔

株式会社studio C 代表取締役

これまで4回の事業譲渡を実現。上場企業にてエンジニア、制作ディレクション、SEO事業立ち上げを歴任。副業で始めた複数の掲示板サイトを国内最大規模まで成長させて事業譲渡。日本のM&Aに透明性と精度をもたらすべく、デジタル事業のM&Aプラットフォーム『RIKKA M&A』を立ち上げ。