ADFSでOpenID Connectアプリを外部SAML IdPにも対応させる完全ガイド|HRD振り分けとトークン変換・クレーム設計の実践

社内は ADFS の OpenID Connect(OIDC)で統一できているが、取引先や委託先は自社の SAML 2.0 IdP を使いたい――そんな“プロトコル混在”の現場は珍しくありません。本記事は、既存の ADFS 登録済み OIDC アプリに外部 SAML IdP 認証を安全に追加するための設計指針・実装手順・トラブル対処までを、実運用の視点で徹底解説します。

目次

シナリオと到達目標

到達目標は「同じ Web / ネイティブ アプリで、社内ユーザーは従来どおり OIDC(AD FS)で、外部ユーザーは各社の SAML 2.0 IdP でサインインできる」構成です。AD FS を「ブリッジ(中継)」として用い、外部 SAML のアサーションを AD FS 側で受け、アプリが理解できる OIDC の ID トークン(JWT)へ発行し直します。これによりアプリ側は issuer(iss)= 自社 AD FS からのトークンだけを検証すればよく、実装が簡潔でセキュアになります。

全体アーキテクチャ(要点)

  • Claims Provider(CP): 外部 SAML IdP を AD FS に「クレーム プロバイダー信頼」として登録。
  • Relying Party(RP): アプリは AD FS に対する RP(OIDC クライアント)として既存利用。必要に応じて同一サービスの SAML 用 RP を追加(アプリが SAML も直接受けたい場合のみ)。
  • HRD(ホーム レルム ディスカバリ): ユーザー属性やクエリ パラメータで内部/外部を自動振り分け。
  • トークン変換: 外部からの SAML アサーションを AD FS で検証→アプリ向けに OIDC ID トークンを発行。

採用しやすい実装パターン比較

パターン概要長所留意点適合場面
A. ブリッジ構成
(推奨)
外部 SAML IdP を CP として追加。既存 OIDC クライアント(アプリ)はそのまま。AD FS が SAML→OIDC 変換。アプリは OIDC のまま・発行者統一・保守容易。クレーム マッピング設計が必要。最も一般的。将来の外部 IdP 追加にも強い。
B. 併存構成同アプリで OIDC と SAML の両 RP を AD FS に作成し、UI で入口を切替。既存パートナーの SAML フローを崩さない。アプリ側も SAML を理解する必要。運用複雑化。歴史的理由で SAML を直接受けたい場合。
C. SAML Bearer Assertion 交換外部 IdP が発行した SAML アサーションをアプリが AD FS トークン エンドポイントへ持ち込み、OIDC トークンに交換。コード/バックエンドで柔軟に制御可能。実装負荷が高い。適合/制約の確認が必要。API 中心・B2B マシン間連携など。

事前に決めておくべきこと(成功の鍵)

  • 一意なユーザーキー(sub)の定義: 例)社内=UPN、外部=メールアドレスまたは SAML の persistent NameID。
    両者が衝突しないよう、外部は identityprovider と組み合わせてアプリ側でネームスペース分離するのが堅実。
  • 必要クレームの最小化: email / name / given_name / family_name / groups / role など。最小権限の原則で。
  • MFA の取り扱い: 外部 IdP の多要素を AD FS 側へ伝播する場合は authnmethodsreferences(http://schemas.microsoft.com/claims/authnmethodsreferences)を活用。
  • ライフサイクル: 外部 IdP の署名証明書ロールオーバー、メタデータ有効期限の監視、障害時フェールセーフ。

実装手順(詳細)

手順の俯瞰

手順概要重要ポイント
1外部 IdP を Claims Provider Trust として追加メタデータ URL/XML を登録。署名証明書の信頼と有効期限の監視を設定。
2アプリの RP 整備(既存 OIDC の確認/必要により SAML RP を追加)OIDC のリダイレクト URI、SAML の ACS URL を正確に設定。スコープ/クレームの設計。
3HRD(ホーム レルム ディスカバリ)で内部/外部を振り分けUPN ドメイン、whr/login_hint、クエリ ?idp= などで分岐。UI ボタン実装も可。
4トークン変換(SAML → OIDC)受入クレーム整形 → OIDC ID トークン発行規則でマッピング。identityprovider も発行。
5アプリ側の実装/設定iss は自社 AD FS で固定。sub の一意性ポリシー、identityprovider によるテナント分離、ロール/権限の割当。

1. 外部 IdP を「クレーム プロバイダー信頼」に追加

GUI の例

  1. AD FS 管理コンソール > 信頼関係 > クレーム プロバイダー信頼 > 追加。
  2. 外部 IdP のメタデータ URL または XML を指定。
  3. 発行者名(表示名)をわかりやすく命名(例:「Partner-Contoso」)。
  4. 証明書(署名)を確認し、メタデータの自動更新を有効化。

PowerShell の例

# メタデータから CP を追加
Add-AdfsClaimsProviderTrust `
  -Name "Partner-Contoso" `
  -MetadataUrl "https://idp.contoso.example/saml/metadata" `
  -AutoUpdateEnabled $true

# 監査のため表示名などを整理

Set-AdfsClaimsProviderTrust -TargetName "Partner-Contoso" -OrganizationInfo "Contoso Ltd." 

受入(Acceptance Transform)クレーム規則

外部 IdP から受けた SAML アサーションを社内標準のクレームへ合わせます。最低限、email/nameidentifier/givenname/surname を正規化しましょう。

# NameID が email 形式なら email クレームへ
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
   Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"]
     == "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"]
 => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value = c.Value);

# givenName, surname をパススルー

c:[Type == "[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname)"]
=> issue(claim = c);
c:[Type == "[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname)"]
=> issue(claim = c);

# 外部 IdP 名を保持(後段の発行で使う)

# identityprovider は AD FS が自動付与するが、明示的に発行する場合

c1:[Type == "[http://schemas.microsoft.com/identity/claims/identityprovider](http://schemas.microsoft.com/identity/claims/identityprovider)"]
=> issue(claim = c1); 

2. アプリの RP 整備(OIDC 既存の確認/必要により SAML RP 追加)

AD FS 2016/2019 以降では、OIDC/OAuth クライアントは「アプリケーション グループ」で管理され、SAML は「リライング パーティ信頼」です。本記事では便宜上まとめて「RP」と表記します。

OIDC クライアント(既存)の確認ポイント

  • リダイレクト URI(https://app.example.com/callback など)が最新の本番/検証 URL と一致。
  • 必要スコープ(openid、profile、email など)を定義。
  • ID トークン発行規則(後述)で、アプリが期待するクレームを発行。

(任意)SAML RP の追加

アプリが SAML も直接受けたい場合のみ、SAML 用の RP を追加します(ACS URL、エンティティ ID、署名/暗号化証明書を設定)。

# SAML RP を手動定義する例(メタデータが無い時)
Add-AdfsRelyingPartyTrust `
  -Name "App-SAML" `
  -Identifier "urn:example:app" `
  -AssertionConsumerServiceUrl "https://app.example.com/SAML/acs"

3. HRD(ホーム レルム ディスカバリ)による内部/外部の自動振り分け

AD FS では、HRD を有効化し、OIDC/SAML/WS-Fed いずれでもクエリ whr(Home Realm)で IdP を指定できます。UX を損ねない分岐が肝です。

実装パターン

パターン手法例向き/不向き
自動振り分けUPN ドメインで判断@partner.example は外部へ、@corp.local は内部メールドメインが明確なときに有効
パラメータ指定whr / login_hint / ?idp=/adfs/oauth2/authorize?...&whr=Partner-Contosoディープリンクや B2B 専用入口に最適
UI 切替AD FS ログインページに「社内」「外部」ボタンボタンで whr 付与 URL に遷移利用者が混在する汎用入口で有効

設定例(PowerShell)

# HRD(ドメイン ヒント)を使えるよう Web 設定を確認
Set-AdfsWebConfig -HRDSelectionEnabled:$true

# ログインページに「外部ログイン」ボタンを追加するカスタム テーマの準備(抜粋)

New-AdfsWebTheme -Name "CorpTheme" -SourceName "Default"
Set-AdfsWebTheme -TargetName "CorpTheme" -AdditionalFileResource @{Uri="/adfs/portal/script/onload.js";Path="C:\adfs\theme\onload.js"}
Set-AdfsWebConfig -ActiveThemeName "CorpTheme" 

onload.js のイメージ(抜粋)

document.addEventListener("DOMContentLoaded", function () {
  var ext = document.createElement("a");
  ext.textContent = "外部パートナーでサインイン";
  ext.className = "btn btn-secondary";
  // Partner-Contoso という CP 名を whr で指定
  ext.href = window.location.origin + "/adfs/ls/?whr=Partner-Contoso";
  var container = document.querySelector("#content");
  if (container) container.prepend(ext);
});

4. トークン変換(SAML → OIDC)とクレーム マッピング

外部 IdP から受けたクレームを、OIDC の ID トークン(JWT)として発行する規則を設定します。AD FS 2019 以降なら GUI で「ID トークン発行変換規則」「アクセス トークン発行変換規則」を編集できます。

代表的なマッピング方針

OIDC クレーム元クレーム(受入)変換/注意点
subnameidentifier(または emailaddress)一意性が重要。外部は persistent NameID を推奨。
namegivenname + surname無ければ displayname を採用。
emailemailaddressドメイン検証・禁止ドメイン遮断を推奨。
amr(認証方法)authnmethodsreferences外部 IdP 側 MFA を表現。ポリシー判定に利用可。
identityproviderAD FS 既定外部/内部の識別に必須。監査にも有用。

ID トークン発行規則(例)

# sub を NameID から作成(SAML 側が persistent ID を出す前提)
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]
 => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Value = c.Value);

# email をパススルー

c:[Type == "[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress)"]
=> issue(claim = c);

# 氏名の整形(givenname/surname がある場合)

c1:[Type == "[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname)"],
c2:[Type == "[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname)"]
=> issue(Type = "[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name](http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name)", Value = c1.Value + " " + c2.Value);

# 認証方法参照(MFA 伝播)

c:[Type == "[http://schemas.microsoft.com/claims/authnmethodsreferences](http://schemas.microsoft.com/claims/authnmethodsreferences)"]
=> issue(claim = c);

# identityprovider を発行(AD FS 既定のものを明示)

c:[Type == "[http://schemas.microsoft.com/identity/claims/identityprovider](http://schemas.microsoft.com/identity/claims/identityprovider)"]
=> issue(claim = c); 

※ クレーム ルール言語では単純な加算連結が効かない環境があります。その場合は 名前 の作成を displayname に切り替えるなど、環境に合わせて調整してください。

(参考)SAML Bearer Assertion グラントの考え方

外部 IdP で認証後、アプリ(サーバー側)が受け取った SAML アサーションを AD FS のトークン エンドポイントへ提出し、OIDC トークンに交換する方式です。プロトコルの理解と実装が必要なため、まずは前述の「ブリッジ構成」を推奨します。

POST /adfs/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&
assertion=BASE64ENCODED_SAML_ASSERTION&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
scope=openid profile email 

5. アプリ側の実装ポイント

  • トークン検証: iss(発行者)は常に自社 AD FS。署名鍵は AD FS の公開鍵のみを信頼リストに。
  • 外部/内部の区別: ID トークンの identityprovider(例:AD AUTHORITY / Partner-Contoso)で判定。
  • ユーザー一意性: sub の衝突を避ける。内部は UPN、外部は persistent NameID/email を標準とし、必要に応じてアプリ側でテナント名と組合わせて論理キー化。
  • 権限付与: 役割(role)やグループ(groups)は最小権限で。外部は専用ロールへスコープを限定。
  • UX: サインイン入口を 1 本にまとめる場合も、UI で「社内」「外部」を選択できる動線を用意すると問合せが減ります。

セキュリティとコンプライアンスの勘所

  • 証明書ロールオーバー監視: 外部 IdP のメタデータ有効期限と署名証明書の期限を定期監視。AD FS のメタデータ自動更新が失敗した場合の手順書を整備(手動入替)。
  • MFA の扱い: 外部 IdP 側の MFA を尊重するか、AD FS 側で追加の MFA を強制するかを明確化。authnmethodsreferences を用い、アプリ側で高リスク操作時のみ MFA 必須にする設計も可。
  • NameID 形式整合: 外部と内部で NameID 形式(email/persistent/unspecified)を合わせて、sub の一貫性を確保。
  • 監査ログと個人情報: ログ保管場所・期間・マスキングのポリシーを定義。誰がどの IdP で認証したか を追跡可能に。
  • セッションとログアウト: OIDC の end_session_endpoint によるログアウトと、外部 SAML IdP 側のセッションをどう扱うか(完全連動するか分離するか)を決める。

テスト計画とトラブルシューティング

テスト観点

  • 外部/内部ともに 1 回目サインイン成功、その後のリダイレクトと Cookie の整合性(SameSite 属性含む)。
  • HRD の自動/手動分岐が期待どおりか(whr、UPN ドメイン、UI ボタン)。
  • ID トークンの中身(sub/email/identityprovider/amr)が要件通りか。
  • 外部 IdP 側のユーザー属性不足(例:surname が無い)時のリカバリー。
  • 時計ずれ(NotBefore/NotOnOrAfter)に対する寛容度(スキュー)。

ログと解析

  • AD FS 要求の追跡: 管理コンソールから有効化し、関連するトークン処理のトレースを収集。
  • イベント ビューア: アプリケーションとサービス ログ > AD FS/Admin。イベント ID 364(トークン発行失敗)は最初に確認。
  • ブラウザ/ネットワーク: 開発者ツールや Fiddler で SAMLResponse と OIDC の id_token を比較。

よくあるエラーと対処

症状原因対処
イベント 364 / 署名検証失敗外部 IdP の署名証明書が更新済みメタデータを再取得、手動で証明書を更新
HRD がループwhr 指定ミス/CP 名不一致CP の表示名と whr 値を一致させる
クレーム不足でアクセス拒否外部 IdP から必須属性が来ない受入規則で代替を定義/外部 IdP へ属性要求
アプリで audience 不一致クライアント ID/リダイレクト URI 相違AD FS とアプリの登録値を再同期
時間不一致で無効サーバー間の時刻同期不良NTP を統一、許容スキューを見直し

実運用のベストプラクティス

  • パートナーごとに CP を分離: CP 名に企業名を付け、権限と監査を分ける。
  • Access Control Policy: 外部は発行先 RP を限定、特定クレーム所持者のみ発行許可。
  • スコープ設計: openid を最小単位に、必要時のみ email/profile を要求。
  • サンドボックス: まずは検証用 AD FS/アプリで受入規則・発行規則を完成させてから本番へ。
  • ドキュメント化: CP メタデータ URL、証明書期間、連絡先(技術/運用)を台帳化し、年次レビュー。

実践コピペ用:代表コマンド集

# --- 外部 CP の作成 ---
Add-AdfsClaimsProviderTrust `
  -Name "Partner-Fabrikam" `
  -MetadataUrl "https://idp.fabrikam.example/federationmetadata/2007-06/federationmetadata.xml" `
  -AutoUpdateEnabled $true

# --- OIDC クライアントの作成(機密クライアント例) ---

Add-AdfsClient `  -Name "App-OIDC"`
-ClientId "app-client-id" `  -ClientSecret (ConvertTo-SecureString "your-secret" -AsPlainText -Force)`
-RedirectUri @("[https://app.example.com/callback](https://app.example.com/callback)")

# --- HRD を有効化 ---

Set-AdfsWebConfig -HRDSelectionEnabled:$true

# --- ログ強化(必要に応じて) ---

Set-AdfsProperties -LogLevel Verbose 

設計チェックリスト(配布可)

  • [ ]外部 IdP メタデータ/証明書/連絡先を入手し台帳化した
  • [ ]CP を作成し、受入クレームを標準化した
  • [ ]OIDC クライアント(既存)のスコープ/リダイレクト URI を見直した
  • [ ]HRD ルール(UPN ドメイン or whr)を決め、UI に反映した
  • [ ]ID トークン発行規則で sub/email/identityprovider を発行
  • [ ]MFA の扱い(外部伝播 or 追加要求)を決定しテストした
  • [ ]監査・障害対応(イベント 364、メタデータ更新失敗時)の手順を整備した
  • [ ]本番前にパートナーと相互テストを実施し、スクリーンショットとトークン例を共有した

まとめ

既存の ADFS + OIDC アプリに外部 SAML IdP を安全に取り込む最短ルートは、外部 IdP を Claims Provider として追加し、HRD で振り分け、AD FS で SAML→OIDC をブリッジすることです。アプリは発行者を AD FS に一本化でき、セキュリティ監査も容易になります。
クレーム設計(sub の一意性)と HRD 体験(自動/手動の併用)を先に固めれば、導入はスムーズに進みます。あとはメタデータと証明書のライフサイクル管理、ログ/監査の運用を整えれば、内外ユーザーが同じサービスを無理なく安全に利用できる体制が完成します。

付録:要点のコンパクト表

項目推奨/要点
外部 IdP 追加CP(クレーム プロバイダー信頼)としてメタデータ登録、自動更新
アプリの RPOIDC は既存を継続。SAML RP は必要時のみ
HRDwhr / ドメイン / UI ボタンの併用
トークン変換SAML 受入 → ID トークン発行規則で OIDC へマップ
アプリ実装iss=AD FS 固定、identityprovider で外部判定、権限は最小
セキュリティ証明書ロールオーバー監視、MFA 伝播/強制、監査ログ整備
トラブル対応イベント 364、クレーム不足、HRD ループ、時間ずれを優先確認

FAQ(現場でよく出る疑問)

Q: アプリのサインイン URL は 1 本にできますか?
A: 可能です。入口は OIDC の認可エンドポイント 1 本にまとめ、HRD で外部を外部 IdP へ、内部は AD FS へ誘導します。アプリ側は受け取る ID トークンの identityprovider で発行元(内部/外部)を判断できます。

Q: 外部パートナーごとに権限を分けたい。
A: CP ごとに identityprovider が異なるため、それをロール付与のキーとして使う設計が簡潔です。必要に応じてパートナー固有のグループ/属性をマッピングして権限を分岐します。

Q: 将来 Azure AD / Entra ID など別 IdP を追加したい。
A: 新しい IdP ごとに CP を追加し、HRD のルールに合流させるだけで対応できます。アプリは AD FS 発行の OIDC トークンを受けるだけなので変更は最小です。


本記事の手順・ルール例は、AD FS 2016/2019 以降の一般的な構成を念頭に解説しています。実運用では環境差(既存規則やポリシー)に合わせて調整してください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次