Azure AD から名前が変わった Microsoft Entra ID で OIDC によるシングルサインオンと SCIM プロビジョニングを同時に使うケースは増えています。しかし「OIDC のユーザー」と「SCIM のユーザー」をどうやって同一人物としてひも付けるか、特に externalId と OIDC クレームのどれを対応させるかで悩みがちです。本記事では、現場でのトラブル例も踏まえつつ、「externalId には oid(必要なら tid:oid)」という設計方針を、実装手順と注意点まで含めて詳しく解説します。
OIDC ユーザーと SCIM ユーザーのひも付けが重要な理由
Microsoft Entra ID(旧 Azure AD)でよくある構成が、
- 認証・ログインは OIDC(OpenID Connect)
- ユーザー追加・削除・属性同期は SCIM(System for Cross-domain Identity Management)
という組み合わせです。技術スタックとしては分離されていても、アプリケーション側から見ると「ログインしてきたユーザー」と「SCIM でプロビジョニングされたユーザー」は同一人物である必要があります。ここを適当に設計すると、次のような問題が発生します。
- ログインはできるが、SCIM 側ユーザーと紐付かず権限が無い(ロールが見つからない)
- 同一人物が別ユーザーとして二重に作られ、データが分散・重複する
- メールアドレスや UPN 変更時に、別人として扱われてしまう
- アプリの再登録やテナント移行時に、すべてのユーザーリンクが壊れる
これらを避けるために重要なのが、「人を特定するための、一意で変わらないキー」を OIDC と SCIM の両方で共有することです。その代表格が、Microsoft Entra ID のユーザー ObjectId を表す OIDC の oid クレームです。
SCIM externalId と OIDC クレームの役割整理
まずは、登場人物である SCIM の externalId と OIDC の主なクレーム(sub/oid/tid など)の役割を整理しておきます。
| 項目 | 所属レイヤー | 意味 | 一意性・不変性のイメージ |
|---|---|---|---|
externalId | SCIM(ユーザープロビジョニング) | 外部システム側のユーザーIDを保持するためのフィールド | プロビジョニング元と先でユーザーを突き合わせるための「変わらないキー」にするのが前提 |
sub | OIDC トークン | Subject。IdP が定める「ユーザーを表す ID」だが、pairwise の場合はクライアントごとに変化 | クライアント ID が変わると変わり得る。SCIM 用の永続キーには不向き |
oid | OIDC トークン(Microsoft Entra ID 拡張) | ユーザーオブジェクトの ObjectId(テナント内で一意な GUID) | テナント内で不変(一度作られたユーザーの ObjectId はそのユーザーのライフサイクル中変わらない) |
tid | OIDC トークン | Tenant ID(テナントを表す GUID) | テナント単位で一意。oid と組み合わせると全テナント横断でも一意になる |
userPrincipalName / email | ユーザー属性 | ログイン名・メールアドレスなど人間が読むための ID | 結婚・組織変更・ドメイン移行などで変わり得るため、照合キーとしては不安定 |
SCIM プロバイダー側(あなたのアプリ)から見ると、externalId に何を入れるかで、その後の運用難易度が大きく変わります。外してはいけないポイントは、
- 「人間にとっての意味」よりも「システムにとっての一意性・不変性」を優先する
- 可能な限り IdP 側(ここでは Entra ID)の内部キーをそのまま借りる
ということです。
結論:externalId には OIDC の oid(= ObjectId)を使う
結論から言うと、Microsoft Entra ID と連携する場合、
- SCIM の
externalIdに、OIDC のoid(ユーザー ObjectId)を入れる - テナントをまたぐ可能性があるなら
externalId = <tid>:<oid>のように複合キー化する
という設計が最も安全で、実務でも採用例が多いパターンです。
逆に、次のような値は externalId に使うべきではありません。
sub(pairwise subject のまま)- メールアドレス(
mail) - UPN(
userPrincipalName)
これらは「変わり得る」「クライアントに依存する」といった性質を持つため、数年単位で見たときの安定性に欠けます。
識別子候補ごとの向き・不向き
| 候補 | テナント内で一意 | 将来も変わりにくい | アプリに依存しない | SCIM externalId 向きか |
|---|---|---|---|---|
oid(ObjectId) | ◎ | ◎ | ◎ | ◎ 最有力 |
tid:oid | ◎(全テナント横断でも一意) | ◎ | ◎ | ◎ マルチテナントなら推奨 |
sub(pairwise) | ○ | △(クライアント変更で変化) | ×(クライアントに依存) | △〜× |
| メール / UPN | △(重複しない前提) | ×(人事・組織変更で変わる) | ○ | × |
SCIM の externalId は「プロビジョニングの送り手と受け手の間で、ユーザーを一意に突き合わせるためのキー」です。ユーザーが退職するまで変わらない ID を選ぶ、という視点で見ると、Microsoft Entra ID では ObjectId(= oid)を使うのが自然だと分かります。
設計パターン:externalId を oid にするか、tid:oid にするか
現場でよく使われるパターンは次の2つです。
| パターン | externalId の例 | 向いているケース | 注意点 |
|---|---|---|---|
| シンプルパターン | e1e2f3f4-...(oid のみ) | 1テナントのみからプロビジョニングされるアプリ | 将来別テナントからも同じ SCIM エンドポイントを使う場合、衝突に注意 |
| 複合キー パターン | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:ffffffff-... | 複数テナントを束ねる SaaS / マルチテナントアプリ | アプリ内部で tid と oid を分解できるようにしておくと後々便利 |
マルチテナント SaaS の場合は、将来の拡張性も考えて最初から tid:oid 形式にしておくことをおすすめします。文字列長は少し増えますが、構造がシンプルで衝突リスクもほぼゼロにできます。
Microsoft Entra ID での実装手順(OIDC + SCIM)
ここからは「Microsoft Entra ID を IdP にしている」「OIDC でログイン」「SCIM でユーザー同期」という前提で、具体的な実装手順を順番に解説します。
1. OIDC アプリ側で oid / tid を受け取る
まず、アプリケーションが受け取る ID トークン(またはアクセストークン)に、oid と tid が含まれているか確認します。Entra ID の OIDC アプリであれば、ユーザーとしてログインした ID トークンには標準で含まれる構成になっていることが多いです。
典型的な ID トークンのペイロード例(簡略化):
{
"aud": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"iss": "https://login.microsoftonline.com/<tid>/v2.0",
"sub": "Az2J9-...pairwise-subject...",
"oid": "ffffffff-1111-2222-3333-aaaaaaaaaaaa",
"tid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"preferred_username": "[email protected]",
"name": "Example Taro"
}
アプリケーション側のユーザーモデルには次のようなフィールドを用意しておくと扱いやすくなります。
- internalUserKey:
oidもしくはtid:oidをそのまま保存するフィールド - displayName:
nameなど、人間向けの表示名 - loginName:
preferred_usernameなど、画面表示・ログを見やすくするための値
ログイン処理では「internalUserKey に紐づくアカウントを検索する」という形にしておくことで、メールアドレスや UPN が変わっても同一人物として扱い続けられます。
2. SCIM プロビジョニングの属性マッピング設定
次に、Entra ID の「エンタープライズ アプリケーション」から SCIM プロビジョニングの属性マッピングを設定します。ポイントは、ターゲット側(あなたのアプリ)の externalId に、ソース側(Entra)ユーザーの user.objectId をマッピングすることです。
典型的なマッピング例は次のようになります。
| ソース属性(Entra) | ターゲット属性(SCIM) | 用途 |
|---|---|---|
userPrincipalName | userName | アプリ側のログイン名・識別名 |
mail | emails[type eq "work"].value | ワークメールアドレス |
givenName | name.givenName | 名 |
surname | name.familyName | 姓 |
user.objectId | externalId | ユーザー突合用の一意キー |
実際の UI では、属性マッピングの一覧から externalId の行を選び、ソース属性として user.objectId を指定します。これで、SCIM Provisioning がユーザーを作成・更新するたびに、アプリ側の SCIM ユーザーに Entra の ObjectId が紐づきます。
3. 複合キー(tid:oid)を externalId に入れる場合
複数テナントを束ねる SaaS の場合は、tid と oid を連結した文字列を externalId に入れるのがおすすめです。
Entra ID の属性マッピングでは、「式(Expression)」を使って属性を加工できます。例えば次のようなイメージです(実際の関数名や構文は環境により異なりますが、考え方として)。
Join(":", [tenantId], [objectId])
このような式を externalId のマッピングに設定しておくと、ターゲット側(SCIM)の externalId には常に <tid>:<oid> 形式の文字列が入るようになります。アプリ側では、ログイン時の ID トークンから tid と oid を組み立て、同じ形式の文字列でユーザー検索を行えばよい、というシンプルな設計にできます。
4. SCIM 側の実装:externalId を「軸」にする
SCIM サーバー(あなたのアプリ側)の実装では、externalId を次のように扱うと運用しやすくなります。
- SCIM ユーザー作成時(POST /Users):
externalIdが指定されていれば、それを内部ユーザーの外部キーとして保存 - 更新時(PATCH /Users):
externalIdで対象ユーザーを検索し、存在すれば更新/なければ新規作成(Upsert のイメージ) - ログイン時:OIDC の
oid(またはtid:oid)から、externalIdで SCIM ユーザーを検索して内部アカウントに紐付け
SCIM の仕様上、id は SCIM 側(あなたのサービス)の内部 ID、externalId は外部システム由来の ID という役割分担になっています。この設計を守ることで、
- アプリ内部の ID 設計は自由度を保ちつつ
- 外部システム(Entra ID)との突合は安定したキーで行える
という、バランスの良い構成にできます。
既存ユーザーの突合・移行戦略
すでに SCIM 連携を運用していて、externalId にメールアドレスや UPN などを使ってしまっている場合は、一度設計を見直して「oid または tid:oid」に揃える移行が必要になります。
移行の基本パターン
| パターン | 作業内容 | メリット | 注意点 |
|---|---|---|---|
| 一括移行 | 全ユーザーの externalId を事前に oid / tid:oid に更新し、その後 SCIM 同期を再開 | 移行後は一貫したルールで運用できる | 既存データ書き換えのため、慎重なテストが必須 |
| 新規ユーザーのみ新ルール | 既存ユーザーは旧ルールのまま、以降の作成ユーザーの externalId だけ新ルールにする | 移行作業の手間を抑えられる | 長期間にわたり「externalId のルールが二種類混在」するため、アプリ側のロジックが複雑になる |
現実的には、一度メンテナンス期間を設けて「一括移行」を行い、テスト環境で十分に検証してから本番適用するのが無難です。その際、
- Entra ID からユーザーの ObjectId・TenantId の一覧をエクスポート
- SCIM ユーザーの現在の
externalIdとのマッピングテーブルを作成 - アプリ側の永続化データを更新
といった流れで移行作業を進めると、抜け漏れを防ぎやすくなります。
よくある落とし穴と回避策
ここからは、現場でハマりがちなポイントと、その対策をまとめます。
sub をキーにしてしまう問題
OIDC の仕様上、sub は「同じ Issuer(IdP)とクライアント ID の組み合わせ内で一意な ID」であり、Microsoft Entra ID ではデフォルトで pairwise subject(クライアントごとに異なる値)になっていることがあります。この場合、
- アプリを再登録してクライアント ID を変えた
- ステージングと本番でクライアント ID が違う
といったタイミングで sub が別の値になってしまい、SCIM 側と突合できなくなります。SCIM の externalId に sub を入れるのは基本的に NG と考えましょう。
B2B / ゲストユーザーの扱い
Entra ID の B2B コラボレーション(ゲストユーザー)では、
- 招待元テナント(ホームテナント)
- 招待先テナント(リソーステナント)
でユーザーの情報が異なります。OIDC トークンに載る oid は、基本的には「リソーステナント側のユーザー ObjectId」として扱われます。そのため、
- どのテナントからプロビジョニングされているか
- そのテナントにおける
oidは何か
を前提に設計する必要があります。複数テナントから同じ SCIM エンドポイントを使う可能性があるなら、最初から tid:oid 形式を採用しておくのが安全です。
ユーザー削除・再作成時の ObjectId 変更
Entra ID のユーザーを一度完全削除してから作り直すと、同じメールアドレスや UPN であっても ObjectId(= oid)は別物になります。このとき、SCIM 側の externalId を古い ObjectId のままにしておくと、
- SCIM プロビジョニングから見て「別人」と認識される
- 古いアカウントが残ったまま、新しいアカウントが追加される
といった事象が起きます。人事系システムとの連携などで「いったん削除して再登録」が行われる可能性がある場合、削除前に SCIM 側でユーザーを無効化し、必要であれば新しい oid で再度紐付け直す運用を設計しておきましょう。
アプリのみ(クライアント資格情報)トークンの罠
クライアント資格情報フローで取得したトークンには、ユーザーではなくアプリケーションのコンテキストが入ります。その場合、oid はユーザーではなく「アプリケーションの ObjectId」になります。つまり、
- ユーザーを特定するキーとしては使えない
- ログインユーザーコンテキストが無い状態なので SCIM ユーザーとは関係がない
という性質があります。ユーザー照合には必ず「ユーザーとしてログインした OIDC トークン」の oid を使うことを徹底しましょう。
メール / UPN を照合キーに使うリスク
メールアドレスや UPN は、人が読むには分かりやすく、ログイン名としても便利です。しかし、
- 結婚・離婚などによる姓の変更
- 組織改編によるドメイン変更
- 企業合併・ブランド変更
などのイベントで値が変わる可能性があります。もし externalId にメールや UPN を使っていると、そのたびに SCIM 側のユーザーと突合できなくなり、アカウント重複や権限喪失といった事故に直結します。
メールや UPN は「表示用」「ログ検索用」には非常に便利ですが、「永続的な照合キー」としては使わない、という割り切りが重要です。
他 IdP を使う場合の設計指針
Microsoft Entra ID 以外の IdP(Idenity Provider)を使う場合でも、考え方の基本は同じです。
- テナント(もしくは IdP インスタンス)内で一意
- ユーザーのライフサイクル中は変わらない
- アプリケーション(クライアント)に依存しない
という ID を、SCIM の externalId と OIDC のクレームで共通化するのがベストプラクティスです。
もし IdP が OIDC トークンに「内部ユーザー ID」をクレームとして出してくれるなら、その値を externalId に採用すれば OK です。そうでない場合は、次のような設計が考えられます。
| 状況 | 候補となるクレーム | externalId の設計例 |
|---|---|---|
| IdP が永続ユーザー ID をクレームとして出せる | user_id / id / ベンダー固有クレームなど | externalId = <永続ユーザーID> |
永続 ID が無く、sub しか使えない | iss(Issuer)+ sub | externalId = <iss>:<sub> のような複合キー |
| メールアドレスしか一意な情報がない | email | 外部システム側の制約として許容するが、将来的な変更リスクを明示しておく |
IdP 側の仕様でどうしても sub しか使えない場合でも、iss と組み合わせることで「どの IdP のどのユーザーか」を一意に識別しやすくなります。ただし、pairwise subject のようにクライアント依存の仕様になっている場合は、将来クライアント ID を変更した際のリスクが残る点を理解した上で採用する必要があります。
具体的なシナリオ別 FAQ
Q. すでに sub を externalId に使っているが、どうすればいい?
可能であれば、一度メンテナンス期間を取り、oid(または tid:oid)を使う設計に移行することをおすすめします。手順のイメージは次の通りです。
- Entra ID からユーザーの ObjectId と現在の
subの一覧をエクスポート - SCIM 側ユーザーの
externalId(=sub)とマッチングさせ、対応するoidを割り当て - SCIM ユーザーの
externalIdを一括更新 - アプリ側のロジックを
oidベースに変更
移行期間中はログなどをよく観察し、「externalId が見つからない」「ユーザーが二重に作成された」といった症状が出ていないか確認すると安心です。
Q. マルチテナント SaaS で、テナントごとに別の SCIM エンドポイントを用意している
テナントごとに完全に別インスタンスとして扱うなら、シンプルに externalId = oid でも問題ありません。その場合でも、アプリ内部では「どのテナントのユーザーなのか」という情報を別フィールドで持っておき、(tenantKey, externalId) の組み合わせで一意になるように設計しておくと、将来構成を変えたいときにも柔軟に対応できます。
Q. すでにメールアドレスを externalId に使ってしまっているが、今は変える予定がない
短期的には「メールアドレスが変わることは少ないだろう」と割り切る判断もあり得ます。その場合でも、
- 人事・組織変更でメールが変わったときの運用手順を決めておく
- 将来的には
oidベースに移行する計画をドキュメント化しておく - アプリ内部では別フィールドとして
oidを保存し始める
といった準備をしておくと、「いつかやってくる大規模改修」のダメージを最小限に抑えられます。
設計チェックリスト(ひも付け観点)
最後に、実装前後で確認しておきたい項目をチェックリストとしてまとめます。新規実装はもちろん、既存連携の棚卸しにもそのまま使えます。
- OIDC トークンに
oid(必要ならtidも)が含まれているか - アプリ内部のユーザーキーとして、
oidもしくはtid:oidを保存しているか - SCIM の
externalIdに、Entra のuser.objectId(またはtid:oid)をマッピングしているか - ログイン時の照合に
subやメール、UPN を使っていないか - SCIM ユーザーの
externalIdを変更した場合の影響(突合ロジック)が明確になっているか - ユーザー削除・再作成、テナント移行など、ライフサイクルイベント発生時の運用手順が決まっているか
- マルチテナント構成の場合、テナントをまたいだときの一意性(
tid:oidなど)が担保されているか
まとめ:externalId × oid で「壊れない ID 連携」を設計する
OIDC と SCIM を組み合わせるとき、表面的には「どの属性をマッピングするか」という設定の話に見えますが、その本質は「何年経っても壊れないユーザー ID 設計」をどう作るか、という問題です。
- SCIM の
externalIdは「プロビジョニング元と先でユーザーを突き合わせるための不変キー」 - Microsoft Entra ID では、ユーザーを一意に表す ObjectId が OIDC の
oidクレームとして提供される - したがって、externalId には
oid(必要ならtid:oid)を採用し、subやメールアドレスは使わないのが実務上もっとも安全 - マルチテナントや B2B などを考慮する場合は、
tidと組み合わせた複合キーにすることで将来の拡張性を確保できる
最初に少しだけ時間をかけて ID 設計を丁寧に行っておくことで、数年後のアプリ刷新やテナント統合、ブランド変更といった大きなイベントが発生しても、「ユーザーのひも付けが壊れて大混乱」という事態を避けることができます。本記事をベースに、自社の要件に合った「壊れにくいユーザー連携」の設計を検討してみてください。

コメント