Keycloak を外部 IdP として Microsoft 365/SharePoint Online に SSO しようとしたとき、AADSTS90019 が出て先に進めないケースがあります。この記事では「ダイレクト フェデレーション」の前提整理から、URL・クレーム・ドメインのズレで起きやすい落とし穴、Keycloak/Microsoft Entra 側での具体的な確認ポイントまで、実務目線でまとめます。
AADSTS90019 とは何か(結論:テナントの判別に失敗している)
エラー AADSTS90019 は、Microsoft Entra ID が「このサインイン要求をどのテナントとして処理すべきか」を特定できないときに出る代表的なエラーです。Microsoft のエラーコード定義では MissingTenantRealm(テナント識別子を要求から判別できない)に該当します。
SSO の設定がどれだけ正しく見えても、次のどこかで「テナント情報(テナントID・テナント名・ドメインなど)」が欠けたり矛盾すると、Entra 側が宛先テナントを決められずに止まります。
- アクセスしているサインイン URL(テナント固有/共通)
- SAML の
Issuer/Audience/Recipient(ACS)の一致 - メールアドレス(UPN相当)のドメインが、想定されるテナント(またはフェデレーション対象)に紐づくか
最初に整理:あなたがやりたいのは「どの連携方式」か
Keycloak と Microsoft 365/SharePoint の SSO で混乱が起きやすいのは、「似た言葉で別物の方式」が存在するからです。まず目的別に、どれを選ぶべきか整理します。
| 方式 | 主な用途 | 対象ユーザー | Entra 側の設定場所 | ユーザー事前作成 | 典型的な落とし穴 |
|---|---|---|---|---|---|
| ダイレクト フェデレーション(SAML/WS-Fed IdP フェデレーション) | 外部組織の IdP を使ってゲスト/外部ユーザーがサインイン | パートナー/ゲスト(B2B) | Entra ID → External Identities → All identity providers(カスタム) | 原則不要(後述) | テナント種別(workforce/external)で ACS が違う、必須クレーム不足 |
| エンタープライズ アプリ(SAML SSO) | Entra が IdP になって SaaS へ SSO | 主に社内ユーザー(メンバー) | Entra ID → Enterprise applications → Single sign-on(SAML) | 必要(Entra に存在するユーザーで SSO) | 「Entra が IdP」なので、Keycloak を IdP にしたい目的とズレる |
| (補足)M365 向けのドメイン フェデレーション | Microsoft 365 そのものの認証を外部 IdP に委任 | 社内ユーザー(メンバー) | ドメイン認証(PowerShell/Graph 等) | 必要(メンバーとして存在) | SP Entity ID が固定など、仕様制約が多い |
この記事で扱う「ダイレクト フェデレーション」は、主に外部ユーザー(B2B)のサインイン体験を Keycloak に寄せるための仕組みです。Microsoft Learn でも、SAML/WS-Fed IdP フェデレーションは「招待の引き換え(invitation redemption)やセルフサービス サインアップ」で外部ユーザーが自分の IdP でサインインできる、と説明されています。
重要:Entra の「workforce テナント」か「external テナント」かで前提が変わる
Microsoft Entra External ID には大きく workforce テナント(従業員+組織リソースのある通常の Entra)と、external テナント(CIAM 用の別テナント)があります。ここを取り違えると、URL(ACS)が合わずに AADSTS90019 を含むエラーに直行しがちです。
| 項目 | workforce テナント | external テナント(CIAM) |
|---|---|---|
| 主目的 | 従業員と組織リソース、B2B 共同作業 | 外部向けアプリ(顧客/消費者)のサインイン体験 |
| Microsoft 365/SharePoint への SSO | 対応 | 非対応(Microsoft 365 などの Microsoft 1st party への SSO はサポート外) |
| ダイレクト フェデレーションの ACS(外部 IdP 側に設定する値) | https://login.microsoftonline.com/login.srf | https://<tenantID>.ciamlogin.com/login.srf |
Microsoft 365/SharePoint Online を対象にしているなら、基本はworkforce テナントが前提になります。external テナント側の値(ciamlogin.com)を使っている場合は、まずここを疑ってください。
結論:ダイレクト フェデレーションでは「ユーザーを Entra に事前作成」は原則不要
質問で一番多いのが「この構成だと、Keycloak 側ユーザーを Entra(Azure AD)に事前作成しないといけないのか?」ですが、ダイレクト フェデレーション(B2B)前提なら原則不要です。
ただし誤解しやすいポイントがあります。“ユーザーオブジェクトが不要” ではありません。B2B コラボレーションでは、認証方法が何であっても(OTP でも SAML フェデレーションでも)、リソース側テナントにはゲストのユーザーオブジェクトが作成され、これを使ってグループやアプリ、SharePoint サイトへの権限付与を行います。
つまり運用感としては次のイメージです。
- 認証(Authentication):Keycloak(外部 IdP)で実施
- 認可(Authorization):Entra 側に作られるゲスト(ユーザーオブジェクト)で制御
- 作成タイミング:招待の引き換え(invitation redemption)やセルフサービス サインアップ等のフローで作られる
AADSTS90019 が出やすい原因を「構成のどこでズレたか」で絞り込む
AADSTS90019 は原因が一つではありませんが、Keycloak×ダイレクト フェデレーション周りでは「テナント識別に必要な情報が、URL か SAML のどこかで欠けている/食い違っている」ケースが大半です。
| よくある原因 | 何が起きているか | 確認ポイント | 対処 |
|---|---|---|---|
| workforce/external テナントを取り違え | ACS が別物なので、Entra が受け付けない/別のフローに飛ぶ | 外部 IdP(Keycloak)に設定した ACS が ciamlogin.com になっていないか | Microsoft 365 なら login.microsoftonline.com/login.srf 側(workforce)に合わせる |
| 必須クレーム(emailaddress)が不足/名前が違う | Entra がユーザーやドメインを解決できず、テナント判別にも失敗しやすい | SAML アサーションに http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress が入っているか | Keycloak のプロトコルマッパーで「URI 形式の属性名」で送出する |
| Audience / Issuer / Recipient(ACS) の不一致 | 要求と応答が紐づかず、Entra が処理できない | Entra 設定値と 1 文字でも違わないか(大小文字・末尾スラッシュ含む) | トレース結果を“正”として、Keycloak/Entra を一致させる |
| AuthnRequest の Issuer(SP Entity ID)に Keycloak が合っていない | Keycloak が別クライアントとして扱い、想定外の応答になる | SAML-tracer で AuthnRequest の Issuer を実値で確認 | Keycloak のクライアント ID(Entity ID)を実際の Issuer に合わせる(後述) |
| プレースホルダーのまま運用({tenant} など) | tenant context が欠落し、MissingTenantRealm に寄りやすい | 設定画面・メタデータの URL に {tenant} が残っていないか | テナント ID(GUID)またはテナント名で確定させる |
| サインイン開始 URL が “共通” で、テナント誘導ができていない | ユーザー入力やヒントがないと、どのテナントか決められない | ユーザーがどこからサインインを開始しているか | workforce なら tenantid 付きの導線(MyApps など)を使う |
Microsoft が要求する「必須の SAML 属性・クレーム」を先に固定する
ダイレクト フェデレーションでは、外部 IdP(Keycloak)が返す SAML 応答に必須の形式があります。Microsoft Learn では、SAML 2.0 の場合に少なくとも以下を求めています。
- NameID Format:
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent - emailaddress クレーム:
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress(ユーザーのメールアドレス) - ACS(AssertionConsumerService):テナント種別に応じた
login.srfエンドポイント - Audience:
https://login.microsoftonline.com/<tenant ID>/(推奨)
ここで重要なのは、あなたの Keycloak 側で使っている属性名が IDPEmail などの独自名になっていても、Entra が期待する名前(URI)で送られていないと、Entra 側が読めない可能性が高い、という点です。
Keycloak 側の設定ポイント(SAML クライアント)
Keycloak では「Microsoft(Entra)を Service Provider とみなす SAML クライアント」を作る形になります。以下は実務での“事故りやすい”項目を、SAML-tracer の確認観点とセットでまとめたチェック表です。
| Keycloak 設定項目 | 推奨・注意点 | SAML トレースで見る場所 |
|---|---|---|
| Client ID(SP Entity ID) | まずは AuthnRequest の Issuer 実値に合わせるのが最優先です。 ドメインレベル(Microsoft 365 など)では urn:federation:MicrosoftOnline が使われるケースがあり、変更できない旨の案内もあります。 | AuthnRequest の <saml:Issuer> |
| ACS URL(Recipient / Destination) | テナント種別により固定。Microsoft 365/SharePoint 前提なら workforce の login.microsoftonline.com/login.srf に合わせる。 | SAML Response の Destination/Recipient |
| NameID Format | persistent を指定。値はユーザーごとに一意で、変わりにくい識別子にする。 | Assertion 内 <Subject><NameID> |
| emailaddress クレーム | 属性名を http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress(URI)で送る。 値は user@domain 形式で、ドメイン部分が意図したフェデレーション条件と合っていることが重要。 | AttributeStatement 内の属性名と値 |
| 署名(Signature) | 少なくとも SAML Response(または Assertion)へ署名し、Entra に登録した証明書と一致させる。ローテーション運用も考慮する。 | Response/Assertion の署名有無、証明書 |
| 時刻(NotBefore/NotOnOrAfter) | Keycloak と Entra の時計ズレがあると弾かれる。NTP 同期を推奨。 | Conditions の時刻 |
Keycloak で emailaddress クレームを送る具体例
Keycloak の「Protocol Mapper」を使い、メールアドレスを SAML 属性として送るときは、属性名を URI で合わせるのがポイントです。概念的には次のような形を目指します。
<saml2:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
<saml2:AttributeValue>[email protected]</saml2:AttributeValue>
</saml2:Attribute>
既存の IDPEmail のような独自属性を送っていても、それだけでは Entra 側が必須クレームとして扱わない可能性があります。Microsoft Learn の必須クレーム定義に合わせてください。
Entra 側の設定ポイント(ダイレクト フェデレーション)
Entra 側では「外部 IdP(SAML/WS-Fed)」を登録し、どのドメインのユーザーをその IdP に誘導するかを設定します。Microsoft Learn では、外部 IdP を追加する手順や、必要なメタデータ(Issuer URI、パッシブ認証エンドポイント、証明書、メタデータ URL 等)を指定する流れが示されています。
よくある “Entra 側のズレ”
- Issuer URI:Keycloak Realm の Entity ID と一致していない
- Sign-in URL(Passive authentication endpoint):Keycloak の SAML エンドポイントと一致していない
- 証明書:Keycloak が署名に使っている証明書と Entra 登録が一致していない(ローテーション後に放置など)
- テナント種別の取り違え:workforce と external で ACS が違うのに同じ値を使っている
外部組織の IdP URL とドメインが違う場合の DNS TXT レコード
パートナー側の IdP のエンドポイントが対象ドメインと一致しない場合、DNS に TXT レコードを追加して証明するフローが必要になることがあります。Microsoft Learn では TXT レコード例(DirectFedAuthUrl=...)が提示されています。
example.com. IN TXT DirectFedAuthUrl=https://idp.example.net/sso
「Client ID: urn:federation:MicrosoftOnline」は正しいのか問題
この値は昔からよく出てくるため混乱しがちですが、ポイントはあなたのシナリオが「ドメインレベル(Microsoft 365 など)」なのか、「外部 IdP フェデレーション(B2B)」なのかで見え方が変わることです。
- Microsoft の案内では、ドメインレベルのフェデレーションでは SP Entity ID が
urn:federation:MicrosoftOnline(グローバル)として扱われ、テナント固有の Entity ID に変更できない、という回答があります。 - 一方で、外部 IdP フェデレーション(SAML/WS-Fed IdP federation)のドキュメントでは、SAML 応答の Audience をテナント ID を含む値にする推奨や、必要な属性・クレームが整理されています。
実務的に一番安全なのは、SAML-tracer で “実際に Entra から飛んできた AuthnRequest の Issuer” を見て、それを Keycloak のクライアント ID に合わせることです。ドキュメントやブログに書いてある値より、あなたの環境で飛んでいる値が正です。
トラブルシューティング:SAML-tracer で必ず見るべきポイント
AADSTS90019 を潰すときは、設定画面を眺めるよりも「飛んでいる SAML を見て差分をなくす」ほうが速いです。以下のチェックリストで、ズレの箇所を機械的に消していきます。
| チェック項目 | 見る場所 | OK の目安 |
|---|---|---|
| サインイン開始 URL にテナント情報が含まれるか | ブラウザのアドレス、遷移ログ | workforce なら tenantid を含む導線(MyApps 等)も活用できる |
| AuthnRequest の Issuer | Keycloak に入る AuthnRequest | Keycloak クライアント ID と一致 |
| SAML Response の Destination / Recipient | Keycloak から返す Response | ACS(login.srf)と完全一致 |
| AudienceRestriction | Assertion 内 | https://login.microsoftonline.com/<tenantID>/ 等、Entra 側が期待する値 |
| NameID Format / 値 | Subject | persistent で一意、変わりにくい値 |
| emailaddress クレーム | AttributeStatement | URI 名で存在し、値が user@domain 形式 |
| 署名と証明書 | 署名要素 | Entra に登録した証明書と一致(期限・ローテも確認) |
「サインイン導線」も地味に重要(共通エンドポイントの挙動)
workforce テナントの場合、SAML/WS-Fed IdP フェデレーションユーザーは、テナント情報を含まない共通エンドポイントからでもサインインできますが、その場合は途中で「組織にサインイン」などの選択を経て、テナントを特定していく挙動になります。テナント情報を明示した URL(例:MyApps の tenantid 付き)を使うと、テナント特定が安定しやすく、切り分けが楽になります。
「どこからサインインを開始したか」は、AADSTS90019 のような“テナント判別系エラー”では特に効きます。ユーザーにブックマークや古いリンクが混ざっているだけで再現することもあるため、再現手順に使った URL は必ず控えておくのがおすすめです。
それでも直らない場合の切り分け(運用で詰まるポイント)
- Entra のサインインログ:エラーコード以外の Failure reason、Correlation ID を確認(サポート問い合わせにも必須)
- Keycloak のサーバーログ:AuthnRequest をどのクライアントで受けたか、署名・宛先チェックで弾いていないか
- 証明書ローテーション:IdP の署名証明書が更新されたのに Entra 側が追従していない(メタデータ URL 未設定など)
- テナント種別の再確認:external テナントで Microsoft 365 へ SSO しようとしていないか(仕様として不可)
まとめ:AADSTS90019 を最短で潰すための優先順位
- テナント種別(workforce / external)を確定し、ACS(
login.srf)を正しい列の値に合わせる - Keycloak から emailaddress クレーム(URI 名) を必ず送る
- SAML-tracer で Issuer / Audience / Recipient を実値比較し、1 文字単位で一致させる
- ダイレクト フェデレーション(B2B)なら、ユーザーの手動事前作成は原則不要だが、権限付与のためのゲストオブジェクトは作成される(招待/サインアップの流れで)
ダイレクト フェデレーションは「認証を外に出す」だけでなく、「外部ユーザーの導線」「必須クレーム」「テナント種別」まで含めて初めて成立します。AADSTS90019 は派手ですが、原因はたいてい “どこか一つの値がズレているだけ” です。トレースで実値を見て、上から順に潰していけば再現性高く解決できます。

コメント