kosui
岩佐 幸翠 / kosui

テックリード @ 株式会社カケハシ

医療SaaSの共通基盤を開発。TypeScriptと関数型プログラミングで堅牢なシステム設計を実践。

ユーザーの内部IDの発行権を他人に握らせてはいけない

結論

ユーザーの内部IDを自システム以外に委ねるべきではありません。

ユーザーの内部IDの実装について気をつけるべきことを2つ紹介します。

  • 外部サービスが発行したIDを内部IDにするべきではない

  • ユーザーが変更可能な値を内部IDにするべきではない

外部サービスが発行したIDを内部IDにするべきではない

外部サービスが発行したIDを内部IDにすることは避けましょう。

内部IDは、システム内で一意で永続的な識別子であるべきです。

例) 外部IDプロバイダのユーザーID

例えば、GoogleをIDプロバイダとして利用する上で、GoogleアカウントのユーザーIDをそのままユーザーの内部IDとして使用した場合、どんな問題が起こるかを考えてみましょう。

例えば、後からGoogleがユーザーIDの仕様を変更した場合、システム内でのユーザーIDの一意性が保証されなくなります。もちろん、GoogleがユーザーIDを変更することは稀ですが、もしそうなった場合、システム全体に影響が及ぶ可能性があります。

また、後から他のIDプロバイダを追加した場合、ユーザーIDの一意性を保つために、複雑なロジックが必要になるかもしれません。

// 複雑になってしまった例
type UserId = {
  type: 'Google' | 'Facebook' | 'Twitter';
  value: string;
}
SELECT * FROM companies c
LEFT JOIN company_members cm ON cm.company_id = c.company_id
LEFT JOIN users u ON u.id = cm.id AND u.id_type = cm.id_type;
-- id_typeを指定し忘れた場合、誤ったユーザーIDを結合して障害になりそう

例) 外部サービスのIDを内部IDにする場合

先ほどの例は、外部IDプロバイダのユーザーIDをそのまま内部IDに利用してしまう、という非常に特異なケースでした。

しかし、AWS CognitoユーザープールやAuth0などのIDaaSを利用する場合、外部サービスのIDを内部IDとして利用することはよくあります。

そして、そのような場合も、外部サービスのIDをそのまま内部IDとして利用することは避けるべきです。

例えば、AWS CognitoユーザープールのユーザーID (sub) をそのまま内部IDとして利用する場合、以下のような問題が発生する可能性があります。

例) ユーザー名の変更時にIDが変更されてしまう場合

AWS Cognitoユーザープールでは、ユーザー名を変更できません。(別途変更可能な preferred_username 属性を利用することはできます。)

この問題を解決するために、「ユーザー名を変更する場合、内部的にはCognitoユーザーを削除して再登録する」という実装をすることが考えられます。

しかし、CognitoユーザーのID(sub)をそのまま内部IDとして利用している場合、ユーザーを削除して再登録することで、内部IDが変わってしまいます。

このCognitoユーザープールに単一のシステムが依存している場合はまだしも、複数のシステムが依存しているとしたらどうでしょう。どうやって安全に新しいID体系へ移行するのでしょうか?

このような場合、あらかじめ内部IDを別途採番する実装をすることが望ましいです。

ユーザーが変更可能な値を内部IDにするべきではない

ユーザーが変更可能な値をシステムのためのID(内部ID)にすることは避けましょう。

IDは一意で永続的な識別子であるべきであり、ユーザーが変更できる値はその特性に反します。

例) ユーザーの内部ID

ユーザーの内部IDを、ユーザーが変更可能な値にした場合、どんな問題が起こるかを考えてみましょう。

例えば、ユーザーの内部IDをユーザー名にする場合、ユーザーがユーザー名を変更すると内部IDも変わってしまいます。

これにより、以下のような問題が発生します。

  • リレーションシップの破壊

他のデータベースのテーブルや外部システムとのリレーションシップが、ユーザー名の変更によって壊れてしまいます。

  • 監査ログ

ユーザーの行動を追跡するためのログが、ユーザー名の変更によって無効になります。

ユーザーがユーザー名を変更しなければ良いのか?

ユーザーがユーザー名を変更不可とする実装は現実的ではありませんが、「初期リリースでは変更不可」などの意思決定が取られることはあります。

では、その場合には問題は発生しないのでしょうか?

以下の例を見てみましょう。

  1. ユーザーAが foo として登録

  2. ユーザーAが退会

  3. ユーザーBが foo として登録

この場合、他のデータベースのテーブルや外部システムとのリレーションシップがきちんと修正されていないと、ユーザーBがユーザーAのデータにアクセスできてしまう可能性があります。

近年では、個人情報の漏洩は企業の大きな信用毀損リスクとなるため、「内部IDを別途採番する実装コスト」と「個人情報の漏洩リスク」を天秤にかけた場合、後者のリスクが圧倒的に大きいです。

もっと分かりやすく言えば、内部IDの実装をするための(多くても)1週間と、個人情報漏洩の問題を顧客に説明し、再発防止策を講じ、徹底的に調査するための1ヶ月を天秤にかけてみてください。

また、サービスがスケールした場合にデータ分析をする場合、過去の差分データがどのユーザーに紐づいているかを正確に把握するためには、内部IDが必要です。せっかくサービスがPMFを達成しても、得られたデータがゴミになってしまい、次の施策が打てなくなってしまうかもしれません。

Share

kosui
岩佐 幸翠 / kosui

テックリード @ 株式会社カケハシ

医療SaaSの共通基盤を開発。TypeScriptと関数型プログラミングで堅牢なシステム設計を実践。