---
title: "ユーザーの内部IDの発行権を他人に握らせてはいけない"
date: "2025-06-02T22:52:49+09:00"
slug: "posts/2025/06/02/225249"
description: "外部サービスのIDやユーザーが変更可能な値をシステムの内部IDに使うべきでない理由と、そのリスクを具体例で説明します。"
themes: ["architecture"]
image: "https://cdn-ak.f.st-hatena.com/images/fotolife/k/kosui_me/20250602/20250602225125.png"
---

![](https://cdn-ak.f.st-hatena.com/images/fotolife/k/kosui_me/20250602/20250602225125.png)

## 結論

ユーザーの内部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の一意性を保つために、複雑なロジックが必要になるかもしれません。

```typescript
// 複雑になってしまった例
type UserId = {
  type: 'Google' | 'Facebook' | 'Twitter';
  value: string;
}
```

```sql
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を達成しても、得られたデータがゴミになってしまい、次の施策が打てなくなってしまうかもしれません。
