Microsoft Entra ID の SCIM プロビジョニングで OAuth2「クライアント資格情報(Client Credentials)」を選ぶと、接続テストでトークン取得に失敗することがあります。原因は“Authorization: Basic”と本文の client_id/client_secret が同時送信される挙動。本記事では再現・確認方法と、現実的な回避策を具体例つきで整理します。
Entra SCIM の OAuth2 クライアント資格情報が失敗する典型症状
非ギャラリーアプリ(カスタム SCIM)に対してプロビジョニングを構成し、認証方式で OAuth2 の「クライアント資格情報(Client Credentials)」を選択して「接続テスト」を実行すると、トークン取得段階でエラーになり、以降の SCIM 呼び出しに進めないケースがあります。
| 観測できる現象 | よくあるエラー表現 | まず見るべきポイント |
|---|---|---|
| 接続テストが失敗し、プロビジョニングが開始できない | 「Multiple client credentials cannot be specified」 「Cannot supply multiple client credentials」など | トークンエンドポイントへの HTTP リクエスト(ヘッダー+本文) |
| Postman / curl では通るのに Entra からは失敗する | Postman でBasicのみ・本文のみなら成功 | Entra 側が「Basic と本文」を併記していないか |
| トークンサーバーが厳格で、認証方式の重複を拒否する | 400/401 と共に invalid_request / invalid_client 系 | OAuth2 サーバー実装が「1リクエスト1方式」を強制しているか |
原因:Authorization ヘッダーと本文に資格情報が二重送信される
切り分けのためにトークンエンドポイントを Webhook に向けて確認すると、同一リクエスト内で次の両方が送られていることがあります。
- HTTP ヘッダー:
Authorization: Basic <base64(client_id:client_secret)> - 本文(
application/x-www-form-urlencoded):client_idとclient_secret
この状態を、OAuth2 トークンサーバー側が「クライアント認証が複数指定されている」と判断して拒否すると、Entra の接続テストはトークン取得で止まります。Microsoft Q&A でも、Entra の SCIM クラウド プロビジョニングが Basic ヘッダーと本文の資格情報を併用して送ること、また それを無効化できないことが “既知の制限” として明言されています。
仕様面の背景:OAuth2 のクライアント認証は「1リクエスト1方式」
OAuth2(RFC 6749)では、機密クライアント(client_secret を持つクライアント)は認証方式を合意し、各リクエストで複数の認証方式を同時に使ってはいけないとされています。
クライアントシークレットを使う代表的な方式は次の 2 つです。
| 方式名(よくある呼び方) | 送信場所 | 特徴 | サーバー実装の傾向 |
|---|---|---|---|
| client_secret_basic | Authorization ヘッダー(Basic) | HTTP 標準の認証スキームを利用。一般に扱いやすい。 | 対応していることが多い |
| client_secret_post | 本文(form-urlencoded) | 本文に client_id/client_secret を置く。RFC では推奨度が低く、やむを得ない場合に限定する趣旨。 | 対応していても「Basic と併記」を拒否する実装が多い |
つまり、トークンサーバーが RFC の趣旨に忠実(=厳格)であるほど、「Basic と本文を同時に送る」クライアントは弾かれやすくなります。RFC 6749 には、リクエスト不備の例として “複数の認証情報・複数の認証メカニズムを使う” 状況が挙げられており、拒否はむしろ自然です。
現時点の結論:Entra(クラウド)側で二重送信を止める設定はない
ここが最重要ポイントです。Microsoft Q&A(2025-09-03 の回答)では、Entra SCIM のクラウド プロビジョニングは OAuth2 Client Credentials 使用時に Basic と本文を常に送ること、そして それをオフにする設定は提供されていないことが明言されています。つまり「Entra 側の設定だけで解決」は現状できません。
事象を確実に掴む:Webhook でトークン要求を丸ごとキャプチャする
原因が推測ではなく確証になると、関係者(アプリ開発、セキュリティ、ID基盤、SRE)への説明が一気に楽になります。おすすめは「トークンエンドポイントだけ」一時的に Webhook / 自前の受け口へ向けて、ヘッダーと本文を同時に確認する方法です。
- テスト用の受け口を用意(例:RequestBin 系、または自作の HTTPS エンドポイント)
- SCIM アプリの設定で Token Endpoint URL を受け口へ差し替える(Tenant URL も別の受け口にすると、どこまで到達したか分かりやすい)
- 接続テストを実行
- 受け口側で以下を確認
Authorizationヘッダーがあるか- 本文が
application/x-www-form-urlencodedか - 本文に
grant_type=client_credentialsと共にclient_id/client_secretがあるか
| 確認項目 | 見る場所 | 期待(問題が起きるケース) | メモ |
|---|---|---|---|
| Basic 認証 | ヘッダー | Authorization: Basic ... が存在 | Base64 の中身はログに残さない |
| 本文資格情報 | 本文 | client_id と client_secret が存在 | ここも絶対に平文保存しない |
| Content-Type | ヘッダー | application/x-www-form-urlencoded | パースしやすい |
回避策の選び方(結論から)
「Entra 側で止められない」以上、現実的な落としどころは次の 3 系統です。最短で効くのは プロキシで正規化、組織ポリシー次第で トークンサーバー寛容化、ネットワーク事情次第で オンプレミス エージェント経由が候補になります。
| 要件・制約 | おすすめ | 理由 | 注意点 |
|---|---|---|---|
| トークンサーバーを改修できない/審査が通らない | プロキシ/トークンブローカーで正規化 | Entra → サーバー間の“翻訳”で吸収できる | 可用性・監視・秘密情報の取り扱いが増える |
| トークンサーバー側で設定変更できる | 受け入れ緩和(どちらか一方を採用) | 構成が増えず最小コストで直る | 仕様面の説明とセキュリティ評価が必要 |
| SCIM エンドポイントが社内ネットワーク内で、外部公開しづらい | オンプレミス プロビジョニング エージェント | 外向き公開を避けつつ連携できる | VM/Windows サービス運用が必要 |
| 当面は止血だけでいい | Bearer トークン運用の整備 | すぐ動く(既存運用を強化) | 期限切れ・更新漏れの事故が起きやすい |
回避策:トークンサーバー側で“どちらか一方を採用”する(できる場合)
もしトークンサーバーの実装や設定を変更できるなら、最もシンプルなのは「重複が来ても片方だけ採用する」方針にすることです。ポイントは 優先順位を固定することです(運用事故を防ぐため)。
- 推奨の優先順位:Basic(Authorization ヘッダー)を優先し、本文の
client_id/client_secretは無視する - 理由:一般に Basic は広く使われ、本文 credential は推奨度が低い位置づけ(RFC 6749 の趣旨)
実装イメージ(疑似コード)は次のようになります。
if Authorization header is Basic:
parse client_id/client_secret from header
ignore body client_id/client_secret (or require they match, and mismatchは即拒否)
else:
parse client_id/client_secret from body
authenticate client
issue token
“無視”でなく“整合性チェック”を入れたい場合は、ヘッダーと本文が両方あるときは「一致している場合のみ許可」にすると、セキュリティ説明がしやすくなります(ただし実装次第で互換性が落ちるので要検討)。
回避策:プロキシ/トークンブローカーでリクエストを正規化する
トークンサーバーを変えられない場合、現場で一番採用されやすいのがこの方式です。Entra から来たリクエストを受け取り、Basic か本文のどちらか一方に統一して本来のトークンサーバーへ転送します。
パターン選択:どちらを削るか
| 正規化方針 | やること | 向いているトークンサーバー | 実装難易度 |
|---|---|---|---|
| 本文方式に統一(Basic を削除) | Authorization ヘッダーを削除して転送 | client_secret_post のみ許可/Basic を拒否する | 低(Nginx/APIM で簡単) |
| Basic 方式に統一(本文を削除) | 本文から client_id/client_secret を取り除いて転送 | client_secret_basic のみ許可/本文 credential を拒否する | 中〜高(本文書き換えが必要) |
Nginx で Authorization ヘッダーを削除する例(本文方式へ統一)
「本文 credential だけなら成功する」タイプのトークンサーバーに合わせるなら、Authorization ヘッダーを落とすのが手堅いです。
server {
listen 443 ssl;
server_name token-proxy.example.com;
location /token {
proxy_set_header Authorization "";
proxy_pass https://real-token.example.com/token;
# 必要に応じて Host / SNI / 証明書検証なども正しく設定する
proxy_set_header Host real-token.example.com;
}
}
この方式は「本文はそのまま流す」だけなので、実装と運用が軽くなりやすい反面、proxy のアクセスログに client_secret が混入しやすい点が最大の落とし穴です。後段のチェックリストにある通り、ログ設計は先に確定させてください。
OpenResty(Nginx+Lua)で本文から client_id/client_secret を削る例(Basic 方式へ統一)
逆に「Basic だけなら成功する」サーバーに合わせたい場合は、本文の client_id/client_secret を除去します。素の Nginx だけでは本文の編集が難しいため、OpenResty のようにリクエスト本文を扱える構成が現実的です。
location /token {
access_by_lua_block {
ngx.req.read_body()
local args = ngx.req.get_post_args()
-- Basic を使う前提なので本文側を削除
args["client_id"] = nil
args["client_secret"] = nil
ngx.req.set_body_data(ngx.encode_args(args))
ngx.req.set_header("Content-Length", nil)
}
proxy_pass https://real-token.example.com/token;
}
本文を書き換える方式はミスると事故が増えるので、トークン応答の透過(ステータス・本文・ヘッダー)、再試行時の挙動、文字コード・URLエンコードをテストで潰してから本番へ入れるのが重要です。
Azure API Management で Authorization を削除する例
Azure 上で完結させたい場合、API Management のポリシーでヘッダー削除ができます(本文編集は要注意ですが、ヘッダー削除だけなら比較的安全に導入しやすいです)。
<inbound>
<base />
<set-header name="Authorization" exists-action="delete" />
</inbound>
トークンブローカー(小さな中継 API)を作るときの要点
より制御したい場合は、プロキシではなく “トークンブローカー” として実装し、Entra からの要求を受けて本来のトークンサーバーへ 正しい方式のみでリクエストし、レスポンスを返します。
- 入力の検証:Basic と本文が両方来たら、どちらを採用するか固定(または一致のみ許可)
- 秘密情報の非ログ化:ヘッダー、本文、例外ログ、トレースに client_secret を残さない
- レート制限:不正リクエストでトークンサーバーを叩き続けない
- キャッシュ:
expires_inを見て、短時間の連続取得を抑制(障害時の雪崩を防ぐ)
回避策:オンプレミス SCIM エージェント経由に切り替える
ネットワーク要件やセキュリティ要件で SCIM エンドポイントを外部公開できない場合、Microsoft Entra の プロビジョニング エージェント(オンプレミス経由)を検討します。導入手順や前提条件、gMSA 推奨などは Microsoft Learn にまとまっています。
この経路は「クラウドのプロビジョニング サービスが直接社内の SCIM へ到達しない」形を作れるのが利点で、結果的に二重クレデンシャル問題の影響範囲を小さくできます。ただし、VM(または Windows サーバー)運用が増えるため、監視・パッチ適用・証明書・プロキシ設定などの運用面も同時に設計してください。
暫定運用を安全に続けるコツ(Bearer トークン運用)
「今すぐはプロキシも置けない」「トークンサーバーも変えられない」場合、既存の Bearer トークン運用(手動/半自動)を続けること自体は現実的です。ただし、事故りやすいポイントが明確なので、運用を “仕組み化” しておくのが重要です。
- 期限管理:失効日の可視化(カレンダー、監視、担当者引き継ぎ)
- ローテーション手順:更新手順を 1 ページに固定(属人化を排除)
- 影響範囲の最小化:トークンのスコープを SCIM に必要な最小権限へ
- 失敗検知:プロビジョニング失敗(401/403/429/5xx)をアラート化
SCIM 側も合わせて確認:/Users と /Groups の前提
トークン取得さえ通れば終わり、ではありません。SCIM は基本的に /Users と /Groups を中心に、作成(POST)・更新(PATCH/PUT)・削除(DELETE)・検索(GET)を行います。まずは SCIM 側が Entra の呼び出しパターンを想定しているかを、仕様・実装・ログで確認しましょう。Microsoft Learn の SCIM エンドポイント解説も合わせて読むと、期待される挙動の全体像が掴みやすいです。
運用・セキュリティのチェックリスト(絶対に外さない)
| 領域 | チェック項目 | 具体策 |
|---|---|---|
| 秘密情報 | client_secret をログに残さない | アクセスログの抑制、ヘッダー/本文のマスキング、例外ログのサニタイズ |
| TLS | TLS 終端と再暗号化の責任分界 | プロキシで終端するなら後段も HTTPS、証明書検証を有効化 |
| 可用性 | プロキシが単一障害点にならない | 冗長化(複数インスタンス)、ヘルスチェック、メンテ手順 |
| 監視 | トークン取得失敗を検知できる | 4xx/5xx をメトリクス化、閾値アラート、相関IDで追跡 |
| レート制限 | ブルートフォースと雪崩対策 | WAF/Rate limit、invalid_client 増加のアラート、キャッシュ |
| 変更管理 | Entra 側の挙動変更に備える | テスト環境で定期的に接続テスト、リリース情報のウォッチ |
よくある質問
Entra 側で「Basic だけ」「本文だけ」を選べませんか?
現時点では、SCIM のクラウド プロビジョニングにおける OAuth2 Client Credentials では、Basic と本文が併用される挙動で、無効化設定は提供されていないと案内されています。
「複数指定はダメ」というのは、どこに根拠があるのですか?
OAuth2(RFC 6749)に「クライアントは各リクエストで複数の認証方式を使ってはならない」という規定があります。また、エラー原因の例として “複数の認証情報・複数の認証メカニズム” が挙げられています。
Client Credentials 自体は一般的なフローですよね?
はい。Client Credentials は “アプリが自分自身として” 別サービスにアクセスする代表的な OAuth2 フローです(ユーザーは介在しません)。ただし、どのクライアント認証方式を使うかはサーバー側の要件と合意が必要になります。
まとめ
- Entra SCIM(クラウド)で OAuth2「クライアント資格情報」を使うと、Basic ヘッダーと本文 credential が同時送信されることがある。
- 厳格なトークンサーバーは、OAuth2 の趣旨に従い “複数のクライアント認証”として拒否し得る。
- 現時点では、Entra 側で二重送信を無効化する設定はないため、回避は「サーバー寛容化」か「プロキシ/ブローカーで正規化」か「オンプレミス経由」になる。
- どの回避策でも、client_secret をログに残さない・可用性と監視を先に設計するのが成功の鍵。

コメント