はじめに — SSOは答えの半分にすぎません
エンタープライズIAMの話題になると、議論の大半はSSO(Single Sign-On)に集中します。SAMLかOIDCか、どのIdPを使うか、といったテーマです。しかし実際にSSOを本番導入した方なら、すぐに次の疑問に突き当たるはずです。
「ログインはできるけど、アカウントは誰が作るの? 退職者は誰が消すの?」
これがまさに**プロビジョニング(provisioning)**の問題であり、2026年現在、その事実上の標準的な答えが **SCIM 2.0(System for Cross-domain Identity Management)** です。Zero Trustが「identity-first」を掲げる時代において、アカウントライフサイクルの自動化はもはや選択肢ではなく、セキュリティの大前提です。AIエージェントのようなnon-human identityまで管理対象に入ってきた今、手作業のアカウント管理は運用的にもセキュリティ的にも維持不可能になりました。
本記事では、SCIM 2.0の仕様構造から実際のHTTP例、主要IdP(Okta、Microsoft Entra ID、Keycloak)の対応状況、そしてSCIMサーバーを自前で実装する際に陥りやすい罠まで、実務の観点で整理します。
なぜプロビジョニングはSSOと同じくらい重要なのか — JMLライフサイクル
アカウントのライフサイクルは、一般に **JML(Joiner / Mover / Leaver)** モデルで説明されます。
| 段階 | イベント | 必要な作業 | 失敗時のリスク |
| --- | --- | --- | --- |
| Joiner | 入社、新規採用 | アカウント作成、グループ/権限付与 | オンボーディング遅延、生産性損失 |
| Mover | 部署異動、職務変更 | 権限の再調整、グループ変更 | 権限の累積(privilege creep) |
| Leaver | 退職、契約終了 | アカウント無効化、セッション/トークン失効 | 退職者のアクセス、情報漏えい |
SSOは「認証の瞬間」だけを扱います。一方、JMLは「アカウントの一生」を扱います。SSOだけあってプロビジョニングがないと、次のようなことが起こります。
- 新入社員がSaaSアプリごとに管理者へアカウント作成を依頼し、数日を無駄にします。
- 部署を異動した社員が、前の部署の権限をそのまま持ち続けます。監査(audit)で最も多く指摘される項目です。
- 退職者のIdPアカウントはブロックされたのに、JIT(Just-in-Time)で作成されたSaaSローカルアカウントとAPIトークンは生きています。実際のセキュリティインシデントの定番シナリオです。
特にLeaverの処理はセキュリティ事故に直結します。SSOの遮断は「新規ログイン」を防ぐだけで、すでに発行されたセッション・リフレッシュトークン・アプリローカルアカウントは別途失効させなければなりません。この失効を自動化する標準的な経路がSCIMのdeprovisioningです。
SCIM 2.0の仕様構造 — 3つのRFCからなる標準
SCIM 2.0は2015年にIETFで3つのRFCとして標準化されました。
| RFC | タイトル | 役割 |
| --- | --- | --- |
| [RFC 7642](https://datatracker.ietf.org/doc/html/rfc7642) | Definitions, Overview, Concepts, and Requirements | 用語定義、ユースケース、要件 |
| [RFC 7643](https://datatracker.ietf.org/doc/html/rfc7643) | Core Schema | User/Groupリソーススキーマ、拡張モデル |
| [RFC 7644](https://datatracker.ietf.org/doc/html/rfc7644) | Protocol | REST API、フィルター、PATCH、バルク、ページネーション |
構造を図にすると次のとおりです。
+----------------------------+ +----------------------------+
| IdP / HRシステム | SCIM | SP (SaaSアプリ) |
| (SCIMクライアント) | ------> | (SCIMサーバー) |
| | HTTPS | |
| - Okta | | - POST /Users |
| - Entra ID | | - GET /Users?filter= |
| - Keycloak + カスタム | | - PATCH /Users/id |
| - Workday などのHR | | - DELETE /Users/id |
+----------------------------+ +----------------------------+
RFC 7642: 概念 RFC 7643: スキーマ RFC 7644: プロトコル
重要なのは役割分担です。一般的に **IdPがSCIMクライアント**、**SaaSアプリ(SP)がSCIMサーバー** です。「我々のサービスはSCIM対応です」という言葉は、「我々はSCIMサーバーを実装した」という意味になります。
User / Groupスキーマと拡張モデル
Core Userスキーマ
RFC 7643が定義するUserリソースの代表的な属性は次のとおりです。
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "2819c223-7f76-453a-919d-413861904646",
"externalId": "emp-10042",
"userName": "yjkim@example.com",
"name": {
"familyName": "Kim",
"givenName": "Youngju"
},
"displayName": "Youngju Kim",
"emails": [
{
"value": "yjkim@example.com",
"type": "work",
"primary": true
}
],
"active": true,
"groups": [
{
"value": "e9e30dba-f08f-4109-8486-d5c6a331660a",
"display": "platform-team"
}
],
"meta": {
"resourceType": "User",
"created": "2026-06-12T09:00:00Z",
"lastModified": "2026-06-12T09:00:00Z",
"version": "W/\"3694e05e9dff590\"",
"location": "https://api.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646"
}
}
注意深く見るべき属性です。
- **id** — SCIMサーバー(SP)が発行する不変の識別子です。クライアントは決めません。
- **externalId** — クライアント(IdP/HR)側の識別子です。相互マッピングの要であり、社員番号のような不変値を使うのが望ましいです。
- **userName** — サーバー内で一意でなければなりません。メールアドレスを使うことが多いものの、メールアドレスは変更され得るという点が罠です。
- **active** — deprovisioningの中核です。DELETEの代わりにactiveをfalseにするsoft-deleteパターンが一般的です。
- **meta.version** — ETag値で、並行性制御に使われます。
Groupスキーマ
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
"displayName": "platform-team",
"members": [
{
"value": "2819c223-7f76-453a-919d-413861904646",
"display": "yjkim@example.com",
"type": "User"
}
]
}
Groupは単純に見えて、実務では最も厄介なリソースです。数千人が所属するグループのmembersを丸ごとPUTで置き換えるIdPもあれば、PATCHで一人ずつadd/removeするIdPもあり、サーバー実装は両方に耐えなければなりません。
Enterprise User拡張とカスタム拡張
HR属性(部署、社員番号、マネージャーなど)はEnterprise User拡張スキーマで表現します。
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"userName": "yjkim@example.com",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "10042",
"department": "Platform Engineering",
"costCenter": "CC-3120",
"manager": {
"value": "26118915-6090-4610-87e4-49d8ca9f808d",
"displayName": "Jane Doe"
}
}
}
独自の属性が必要な場合は、自身のURN名前空間でカスタム拡張スキーマを定義でき、サーバーは/Schemasエンドポイントでこれを公開します。ただしカスタム拡張はIdP側の属性マッピングUIが対応して初めて実際に使えるため、可能な限りEnterprise拡張の範囲内で解決するほうが互換性の面で有利です。
プロトコル — RESTエンドポイントとHTTP例
RFC 7644が定義するエンドポイントの全リストです。
| メソッド | パス | 用途 |
| --- | --- | --- |
| POST | /Users | ユーザー作成 |
| GET | /Users | 一覧取得 + filter検索 |
| GET | /Users/id | 単一取得 |
| PUT | /Users/id | 全置換 |
| PATCH | /Users/id | 部分更新 |
| DELETE | /Users/id | 削除 |
| GET | /ServiceProviderConfig | サーバー機能の公開 |
| GET | /Schemas | スキーマの公開 |
| GET | /ResourceTypes | リソースタイプの公開 |
| POST | /Bulk | バルク操作(任意) |
ユーザー作成 — POST
POST /scim/v2/Users HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"externalId": "emp-10042",
"userName": "yjkim@example.com",
"name": { "givenName": "Youngju", "familyName": "Kim" },
"emails": [{ "value": "yjkim@example.com", "type": "work", "primary": true }],
"active": true
}
成功時、サーバーは201 Createdとともに、idとmetaが埋められた完全なリソースを返します。userNameがすでに存在する場合は、SCIMエラーボディを含む409 Conflictで応答しなければなりません。
HTTP/1.1 409 Conflict
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"scimType": "uniqueness",
"detail": "userName yjkim@example.com already exists",
"status": "409"
}
フィルター検索 — GETとfilterクエリ
IdPはプッシュの前に「このユーザーはすでに存在するか」をfilterで確認します。最も頻繁に呼ばれるパターンです。
GET /scim/v2/Users?filter=userName%20eq%20%22yjkim%40example.com%22 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
デコードすると次のフィルターになります。
filter=userName eq "yjkim@example.com"
RFC 7644のフィルター文法は、eq、ne、co(contains)、sw(starts with)、gt、ge、lt、le、pr(present)の演算子に加え、and/or/notの結合、括弧、複合属性パスまでサポートします。
filter=emails[type eq "work" and value co "@example.com"]
filter=meta.lastModified gt "2026-06-01T00:00:00Z"
filter=userName sw "yj" and active eq true
とはいえ、実際のIdPが使うフィルターはほぼ「userName eq ...」と「externalId eq ...」の2つです。サーバー実装ではフィルター文法の完全対応を目指すより、まずこの2パターンを正確にサポートし、残りは段階的に拡張する戦略が現実的です。
部分更新 — PATCH
PATCHはSCIMで最も複雑な部分です。add / remove / replaceの3つのopを持つ操作リストを渡します。
PATCH /scim/v2/Users/2819c223-7f76-453a-919d-413861904646 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false },
{ "op": "replace", "path": "name.familyName", "value": "Lee" },
{
"op": "add",
"path": "emails",
"value": [{ "value": "yj.lee@example.com", "type": "work" }]
}
]
}
グループメンバーシップの操作には、value filterを含むpathを使います。
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "remove",
"path": "members[value eq \"2819c223-7f76-453a-919d-413861904646\"]"
},
{
"op": "add",
"path": "members",
"value": [{ "value": "08b0fe34-0ec4-4857-b8e2-58dbccca2f48" }]
}
]
}
全置換 — PUT
PUTはリソース全体を置き換えます。単純ですが危険です。クライアントが知らない属性(サーバーが内部管理する属性)まで消してしまう可能性があるため、サーバーはPUTボディの内容にかかわらずreadOnly/immutable属性を保持しなければなりません。
プッシュモデル vs プルモデル
| 観点 | プッシュ(IdP → SP) | プル(SP → IdP/HR) |
| --- | --- | --- |
| 方向 | 変更発生時にIdPがSPのSCIM APIを呼ぶ | SPが定期的にソースをポーリング |
| 遅延 | ほぼリアルタイム(イベント駆動) | ポーリング間隔に依存(数十分〜数時間) |
| 代表例 | Okta、Entra IDのSaaSプロビジョニング | Entra IDのオンプレHR連携の一部 |
| SPの実装負担 | SCIMサーバーの実装が必要 | SCIMクライアントの実装が必要 |
| 障害処理 | IdPのリトライキューに依存 | 次回ポーリングで自然回復 |
業界の主流は**プッシュモデル**です。OktaもEntra IDも、自社ディレクトリの変更イベントを検知してSPのSCIMエンドポイントへプッシュします。興味深いのはEntra IDの動作で、純粋なイベントプッシュではなく、約40分周期の同期サイクルの中で変更分をまとめてプッシュします。「Entraのプロビジョニングがすぐ反映されない」という問い合わせの大半はこの周期が原因です。
プッシュモデルでSPが必ず考慮すべきは**冪等性**です。IdPはネットワークエラー時に同じリクエストを再試行するため、同じexternalIdのPOSTが2回来ても重複アカウントを作ってはいけません。
主要IdPのSCIM対応状況 (2026)
Okta
- **クライアントとして**: 数千のOIN(Okta Integration Network)アプリへのSCIMプッシュをサポートします。カスタムアプリもSCIM 2.0テンプレートで連携可能です。
- **サーバーとして**: Okta自体もSCIMサーバーAPIを提供しており、外部システムがOktaユーザーをSCIMで管理できます。
- グループプッシュ(Group Push)は別機能として分離されており、グループメンバーシップの同期はPATCHベースです。
Microsoft Entra ID (旧 Azure AD)
- エンタープライズアプリのプロビジョニング機能はSCIM 2.0ベースです。ギャラリーアプリだけでなく「非ギャラリーアプリ + SCIMエンドポイント」の組み合わせもサポートします。
- 同期サイクル(約40分)があり、初期同期と増分同期の区別があります。On-demand provisioningで特定ユーザーを即時プッシュしてデバッグできます。
- EntraのSCIMクライアントは仕様と微妙に異なる点があったことで有名でした(例: 過去のPATCH形式問題)。サーバー実装時のEntra互換性テストは必須です。
Keycloak
- **Keycloak 26.6.x(2026年5月時点で26.6.2)に至ってもSCIMはコア機能ではありません。** これを知らずに「Keycloakを使えばSCIMも使えるだろう」と仮定すると困ったことになります。
- 選択肢は3つです。(1) コミュニティ拡張(scim-for-keycloakなど)でKeycloakをSCIMサーバー化する、(2) KeycloakのイベントリスナーSPIで変更イベントを受けて独自のSCIMクライアントを実装する、(3) Keycloak 26.6のWorkflows機能でrealm内のライフサイクル自動化を一部代替する。
- Keycloak 26.6の[Workflows](https://www.keycloak.org/docs/latest/release_notes/index.html)は「非アクティブユーザーの自動無効化」のようなrealm内部の自動化には有用ですが、外部SPへのプロビジョニングは依然として別実装が必要です。
イベントリスナーSPIでプッシュ型連携を実装する骨格は次のとおりです。
public class ScimPushEventListener implements EventListenerProvider {
private final ScimClient scimClient;
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
if (event.getResourceType() != ResourceType.USER) {
return;
}
switch (event.getOperationType()) {
case CREATE -> scimClient.createUser(toScimUser(event));
case UPDATE -> scimClient.patchUser(toScimPatch(event));
case DELETE -> scimClient.deactivateUser(extractUserId(event));
}
}
@Override
public void close() {
// no-op
}
}
実運用では、イベント消失に備えたアウトボックス(outbox)テーブルとリトライキューを併設するのが安全です。
SCIMサーバー実装時の罠
罠1 — PATCHセマンティクスの複雑さ
PATCHはSCIM実装の難易度の8割を占めます。
- **pathなしのPATCH**: opでpathが省略されると、valueが「部分リソース」となり、属性単位でマージしなければなりません。Entra IDが好んで使っていたパターンです。
- **value filterパス**: members[value eq "..."] のようなパスには、事実上ミニクエリ言語のパーサーが必要です。
- **複数値属性のセマンティクス**: emailsへのaddは追加なのか置換なのか、primaryが2つになったらどうするのかを、仕様(RFC 7643の2.4節)に従って正確に処理しなければなりません。
- **大文字小文字**: SCIM属性名は大文字小文字を区別しません(case-insensitive)。「userName」と「username」を同一として扱う必要があります。意外と多くの実装がこれを見落とします。
自前でパーサーを書くより、実績のあるライブラリ(JavaならUnboundID SCIM 2 SDKなど)の利用をおすすめします。
罠2 — ETagと並行性
HRシステムとIdP管理者が同じユーザーを同時に変更すると、最後の書き込みが勝ちます(lost update)。SCIMはこのためにETagベースの楽観的ロックを定義しています。
PUT /scim/v2/Users/2819c223-7f76-453a-919d-413861904646 HTTP/1.1
If-Match: W/"3694e05e9dff590"
Content-Type: application/scim+json
バージョンが異なる場合、サーバーは412 Precondition Failedを返します。ただし現実には、ほとんどのIdPクライアントはIf-Matchを送らないため、サーバーはETagを「サポートするが強制しない」レベルで実装し、ServiceProviderConfigで正直に公開するのが妥当です。
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
"patch": { "supported": true },
"bulk": { "supported": false, "maxOperations": 0, "maxPayloadSize": 0 },
"filter": { "supported": true, "maxResults": 200 },
"etag": { "supported": true },
"changePassword": { "supported": false },
"sort": { "supported": false },
"authenticationSchemes": [
{
"type": "oauthbearertoken",
"name": "OAuth Bearer Token",
"description": "Authorization header with Bearer token"
}
]
}
罠3 — ページネーション
SCIMのデフォルトのページネーションは、1から始まるstartIndexベースです。
GET /scim/v2/Users?startIndex=101&count=100 HTTP/1.1
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 5042,
"startIndex": 101,
"itemsPerPage": 100,
"Resources": []
}
罠は次のとおりです。
- startIndexは0ではなく**1から**始まります。off-by-oneバグの定番です。
- オフセットベースのため、ページ送りの最中にデータが変わると欠落や重複が発生します。大規模同期中に起きる微妙な不整合の原因です。カーソルベースのページネーション拡張がドラフトで議論されてきましたが、クライアント互換性のためstartIndexは必ずサポートしなければなりません。
- 正確なtotalResultsを計算するcountクエリは、大規模テーブルでは高コストになり得ます。DBインデックス設計に反映すべきです。
罠4 — エラー応答形式
SCIMクライアントはエラーボディのscimTypeを見て動作を分岐します。401/403/404/409/412を正確なSCIMエラー形式で返さないと、IdPが「エンドポイント異常」と判断してプロビジョニングを隔離(quarantine)することがあります。Entra IDのquarantine状態が代表例です。
Deprovisioningとセキュリティ
Leaverの処理はセキュリティ上、最も重要なフローです。推奨設計は次のとおりです。
[HR: 退職処理]
|
v
[IdP: アカウント無効化] ----> SCIM PATCH active=false ----> [SP: soft-delete]
| |
v v
[IdPセッションを全廃棄] [SPセッション/リフレッシュトークン廃棄]
[APIキー、PATの無効化]
[保持期間経過後にhard-delete]
核心となる原則です。
1. **DELETEよりactive=falseを優先** — 監査証跡とデータ保持(法的要件)のため、即時のhard-deleteは避けます。多くのIdPもデフォルト動作は「無効化」です。
2. **SCIMはセッションを殺しません** — active=falseは「新規認証」を防ぐだけです。SPはこのシグナルを受け取ったら、当該ユーザーのアクティブセッションとリフレッシュトークンを能動的に廃棄しなければなりません。このギャップを埋める補完標準がOpenIDの[Shared Signals Framework / CAEP](https://openid.net/wg/sharedsignals/)であり、2026年現在、主要ベンダーが続々と採用中です。
3. **non-human identityも忘れない** — 退職者が作成したサービスアカウント、ボットトークン、AIエージェントへの委任権限はSCIMの範囲外であることが多いです。所有権移転プロセスを別途設計する必要があります。
HR主導プロビジョニングのアーキテクチャ
成熟した組織の最終形は、HRシステムを単一の信頼できる情報源(source of truth)とする構造です。
+-----------+ +---------------------+ +--------------------------+
| HRシステム | --> | IdP | --> | SaaSアプリ1 (SCIMサーバー) |
| (Workday | | (Okta / Entra / | --> | SaaSアプリ2 (SCIMサーバー) |
| など) | | Keycloak) | --> | 社内アプリ (SCIMサーバー) |
+-----------+ +---------------------+ +--------------------------+
入社/異動/退職 グループ/権限マッピング アカウント/権限の反映
イベント発行 規則 (部署 -> グループ セッション/トークン失効
-> アプリロール)
設計ポイントです。
- **属性マッピング規則の一元化**: 「部署コード3120はplatform-teamグループ、platform-teamはアプリXのadminロール」のようなマッピングをIdPの一箇所で管理します。
- **開始日/終了日の事前反映**: HRデータには将来の入社日/退職日があります。入社日前にアカウントを作成しつつ非アクティブにしておき、退職日の0時に自動無効化するスケジューリングが理想です。
- **例外フロー**: 契約社員、パートナー、M&Aで加わった組織など、HRシステムに存在しないアイデンティティのための別の登録経路と有効期限ポリシーが必要です。
テスト戦略
SCIMサーバーをリリースする前に確認すべきテストマトリクスです。
| カテゴリ | テスト項目 |
| --- | --- |
| 作成 | 正常系POST、重複userNameで409、必須属性欠落で400 |
| 取得 | userName/externalIdフィルター、大文字小文字非区別、URLエンコーディング |
| 更新 | PATCH add/remove/replace、pathなしPATCH、value filterパス |
| 置換 | PUT時のreadOnly属性の保持、未指定属性の扱い |
| 無効化 | active=false時のセッション/トークン失効連携 |
| ページネーション | startIndex=1基準、境界値、空の結果 |
| 並行性 | ETag不一致で412、同時PATCH |
| 認証 | 期限切れトークンで401、権限不足で403 |
| 冪等性 | 同一POSTの再試行、同一PATCHの再適用 |
ツールの面では次が活用できます。
- **Microsoft SCIM Validator**: Entra互換性を自動チェックしてくれます。
- **Okta SCIMテスト(Runscopeコレクションベース)**: OIN登録前の必須通過項目です。
- **curlスモークテスト**: CIに組み込みやすい最小検証の例です。
#!/usr/bin/env bash
set -euo pipefail
BASE="https://api.example.com/scim/v2"
TOKEN="$SCIM_TEST_TOKEN"
1. 作成
USER_ID=$(curl -sf -X POST "$BASE/Users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/scim+json" \
-d @fixtures/user-create.json | jq -r '.id')
2. filter検索
curl -sf "$BASE/Users?filter=userName%20eq%20%22scim-test%40example.com%22" \
-H "Authorization: Bearer $TOKEN" | jq -e '.totalResults == 1'
3. 無効化
curl -sf -X PATCH "$BASE/Users/$USER_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/scim+json" \
-d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations":[{"op":"replace","path":"active","value":false}]}' \
| jq -e '.active == false'
4. 後始末
curl -sf -X DELETE "$BASE/Users/$USER_ID" -H "Authorization: Bearer $TOKEN"
echo "SCIM smoke test passed"
ステージング環境に実際のIdPテナント(Okta developer org、Entraテストテナント)を接続しておき、リリースごとに実IdP発のトラフィックで回帰テストを回すのが最も確実です。IdPごとの実装差異はドキュメントを読むだけでは絶対にすべて捕捉できません。
運用ベストプラクティス
- **認証はOAuth Bearerトークンで、トークンはローテーション可能に**: 長寿命の固定トークン1つで運用していて漏えいすると、ディレクトリ全体が露出します。トークンローテーション手順と期限切れ監視を整備します。
- **SCIMエンドポイントには専用のrate limitと監査ログ**: 誰が(どのIdPテナントが)いつ何を変更したかをすべて残します。SOC 2 / ISO 27001監査の定番エビデンスです。
- **プロビジョニングドリフトの検知**: IdPのユーザーリストとSPのユーザーリストを定期的に突合(reconcile)するバッチを設けます。プッシュイベントの消失は必ず発生すると仮定すべきです。
- **スキーマ変更は後方互換で**: すでに連携済みの顧客の属性マッピングを壊さないよう、属性の削除や意味変更は新バージョンのパスに分離します。
おわりに
SCIM 2.0は華やかな技術ではありません。しかしSSOが「入口のドア」だとすれば、SCIMは「名簿の管理」であり、セキュリティ事故の多くはドアではなく名簿で起こります。2026年のエンタープライズB2B市場では、SCIM対応はSSOとともにエンタープライズ商談のチェックリスト項目になって久しいです。
まとめると次のとおりです。
1. SSOとプロビジョニングは別の問題であり、JMLライフサイクル全体を自動化して初めてidentity-firstセキュリティが成立します。
2. SCIM 2.0はRFC 7642(概念)、7643(スキーマ)、7644(プロトコル)の3軸で理解すればよいです。
3. サーバー実装の難所はPATCHセマンティクス、ページネーション、エラー形式、冪等性です。実績あるSDKと実IdP連携テストで対処しましょう。
4. deprovisioningはactive=falseで終わりません。セッション・トークンの失効まで設計する必要があり、長期的にはShared Signals/CAEPのようなイベント標準との結合を見据えるべきです。
次回の記事では、マルチテナントSaaSにおける顧客ごとのSSO設計、そしてその上でSCIMオンボーディングをセルフサービス化する方法を扱います。
参考資料
- [RFC 7642 — SCIM: Definitions, Overview, Concepts, and Requirements](https://datatracker.ietf.org/doc/html/rfc7642)
- [RFC 7643 — SCIM: Core Schema](https://datatracker.ietf.org/doc/html/rfc7643)
- [RFC 7644 — SCIM: Protocol](https://datatracker.ietf.org/doc/html/rfc7644)
- [RFC 6749 — The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749)
- [RFC 9700 — Best Current Practice for OAuth 2.0 Security](https://datatracker.ietf.org/doc/html/rfc9700)
- [Okta — SCIMプロトコルドキュメント](https://developer.okta.com/docs/reference/scim/)
- [Microsoft Entra ID — SCIMエンドポイント開発ガイド](https://learn.microsoft.com/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups)
- [Keycloak Documentation](https://www.keycloak.org/documentation)
- [Keycloakリリースノート (26.6 Workflowsなど)](https://www.keycloak.org/docs/latest/release_notes/index.html)
- [OpenID Shared Signals Framework / CAEP](https://openid.net/wg/sharedsignals/)
- [UnboundID SCIM 2 SDK for Java](https://github.com/pingidentity/scim2)
현재 단락 (1/343)
エンタープライズIAMの話題になると、議論の大半はSSO(Single Sign-On)に集中します。SAMLかOIDCか、どのIdPを使うか、といったテーマです。しかし実際にSSOを本番導入した方な...