はじめに
この記事は、次のような方に向けて書いています。
- MCP サーバーに Microsoft Entra ID 認証を組み込むとはどういうことかを、概念レベルから理解したい
- OAuth の観点で見たとき、MCP サーバーはどんな立ち位置になるのかを整理したい
- 「とりあえず動いた」から一歩進んで、なぜその実装になるのかを腹落ちさせたい
- JWT の検証や OBO フローが、MCP サーバーの役割とどう結びついているのかを理解したい
逆に、
- とにかく今すぐ動かしたい、試してみたい
- どんな実装なのか知りたい
という方は、以下の GitHub リポジトリを見るのが最短ルートです。
なお、最新の OAuth の仕様をキャッチアップできていないので一部内容が古いかもしれません。
最新の仕様を踏まえた実装が必要な方興味ある方の需要には答えられない内容になっている可能性がありますのでご注意ください。
1. この記事で扱うこと
「MCP サーバーに Microsoft Entra ID 認証を実装する」とは、単にログイン機能を付けることではありません。
Microsoft Entra ID や OAuth の観点で考えると、
- MCP サーバーは OAuth2 におけるどの役割なのか?
- トークンは誰が検証するのか?
- なぜ Entra ID に毎回問い合わせないのか?
といった疑問が自然に出てきます。
この記事では、実際の実装コードを読み解きながら、
OAuth の文脈の中で、MCP サーバーは何者で、どんな役割を果たすべきなのか
を整理していきます。
単なる手順書ではなく、「なぜそうなるのか?」を理解するための記事です。
2. OAuth の全体像と登場人物を整理する
MCP サーバーに Microsoft Entra ID 認証を実装する前に、
一旦 OAuth の全体像を整理します。
細かな話に入る前に、
- 誰が
- 誰に
- 何を要求して
- 誰がそれを検証するのか
を明確にしておきます。
2.1 OAuth に登場する 4 つの役割
OAuth2 には、次の 4 つの登場人物がいます。
| 役割 | 今回の構成では誰か | 何をするのか? |
|---|---|---|
| Resource Owner | ユーザー | 自分のデータへのアクセスを許可(同意)する |
| Client | MCP クライアント(Agent / LLM アプリ) | ユーザーの代わりにトークンを取得し、API を呼び出す |
| Authorization Server | Microsoft Entra ID | ユーザーを認証し、アクセストークンを発行する |
| Resource Server | MCP サーバー | 受け取ったアクセストークンを検証し、アクセス可否を判断する |
イメージ的にはこんな感じです。
それぞれを一言で表すと、こうなります。
- ユーザー → 「このアプリにアクセスを許可する人」
- Client → 「トークンを持って API を呼ぶ人」
- Entra ID → 「本人確認をして、通行証(トークン)を発行する人」
- MCP サーバー → 「通行証をチェックして、中に入れるか決める人」
わかりやすさ重視で書いたので、いろいろと説明が緩いです。
厳密ではないので、その点はご容赦ください。
このブログで特に重要なのは、
MCP サーバーは Resource Server(保護されたリソース)である
という点です。
関連ドキュメント:
2.2 全体のやり取りを図で見る
全体の処理内容をシーケンス図で整理すると、このような感じです。
ポイント
🔑 トークンを発行するのは Entra ID
🎫 トークンを持ってくるのは Client
🔐 トークンをチェックするのは MCP サーバー
特に重要なのはここです:
MCP サーバーは「守られる存在」ではなく、
「通行証をチェックする側」である
この理解があると、
- なぜ JWT をサーバー側で検証するのか
- なぜ JWKS を取得するのか
- なぜ OBO フローが出てくるのか
が自然につながっていきます。
補足:トークンイントロスペクションについて
認可サーバーの実装によっては、リソースサーバー側でトークンを検証する代わりに、
トークンイントロスペクションという方式でトークンの検証を認可サーバー側に問い合わせる実装を提供している場合もあります。
ただし、Microsoft Entra ID では JWKS を使った JWT 検証が標準的なアプローチです。
本ブログでは、あくまで Microsoft Entra ID の認証に特化した内容を記載しています。
関連ドキュメント:
2.3 OAuth の役割分担
OAuth には複数の認可フロー(Authorization Code Flow など)があり、
厳密なやり取りやパラメータは多少異なります。
しかし本質的に重要なのは、次の役割分担です。
- アクセストークンの発行 → Entra ID
- アクセストークンの取得と提示 → Client
- トークンを検証し、応答を返すかを決定する → MCP Server
つまり、
Microsoft Entra ID が発行したアクセストークンを、MCP サーバー側で正しく検証する処理を実装すること
これが、「MCP サーバーに Microsoft Entra ID 認証を実装する」という意味になります。
3. どうやって Entra ID のアクセストークンを検証するのか?
Microsoft Entra ID が発行するアクセストークンは、JWT(JSON Web Token)形式です。
JWT は次の 3 つのパーツで構成されています。
Header(Base64).Payload(Base64).Signature
| パーツ | 内容 |
|---|---|
| Header | 署名アルゴリズムやキーID(kid) |
| Payload | ユーザー情報やクレーム(iss, aud, exp など) |
| Signature | 改ざん検知のための署名 |
MCP サーバーでは、この JWT に対して主に 3 つの検証を行います。
関連ドキュメント:
3.1 署名のチェック(改ざん検証)
JWT が改ざんされていないことを確認するために、署名の検証を行います。
Entra ID は、トークン署名検証用の公開鍵を公開しています。
この公開鍵が配置されているエンドポイントを JWKS URI(JSON Web Key Set) と呼びます。
Microsoft Entra ID の場合、JWKS URI は以下の形式です。
https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys
このエンドポイントには複数の公開鍵が含まれています。
アクセストークンのヘッダーには、署名の検証に使う公開鍵の識別子である キーID(kid) が記載されており、
この kid を使って必要な公開鍵を特定する仕組みになっています。
3.1.1 署名検証の流れ
MCP サーバーは以下の手順で署名を検証します。
- JWT の Header から
kidを取得 - JWKS URI から公開鍵一覧を取得
- 対応する
kidの公開鍵を探す - その公開鍵で署名を検証
これにより:
- 改ざんされていないか
- 本当に Entra ID が発行したトークンか
を確認できます。
関連ドキュメント:
3.1.2 メタデータエンドポイントについて
なお、JWKS URI を含めた OAuth / OpenID Connect に必要な各種エンドポイントの情報は、
メタデータエンドポイントとしてまとめて提供されています。
https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration
このメタデータには以下のような情報が含まれています。
-
jwks_uri(公開鍵の取得先) -
issuer(トークン発行者) -
authorization_endpoint(認可エンドポイント) -
token_endpoint(トークン発行エンドポイント)
実装によっては、JWKS URI を直接指定せず、
このメタデータエンドポイントから動的に jwks_uri を取得する方法もあります。
これにより、エンドポイントの変更に柔軟に対応できるメリットがあります。
関連ドキュメント:
3.2 ペイロード(クレーム)のチェック
署名が正しくても、それだけでは不十分です。
Payload(クレーム)を検証し、
- 有効なトークンか?
- この API 向けのトークンか?
- 期限内か?
を確認します。
3.2.1 主なチェック項目
| クレーム | 確認内容 | なぜ必要か |
|---|---|---|
iss |
発行元 | 信頼できる認可サーバーか |
aud |
宛先 | この MCP サーバー向けのトークンか |
exp |
有効期限 | 期限切れでないか |
nbf |
利用開始時刻 | まだ有効でないトークンではないか |
関連ドキュメント:
3.2.2 Entra ID へのアプリ登録と aud の値
なお、MCP サーバーは事前に Entra ID にアプリ登録しておく必要があります。
アプリ登録では以下の設定を行います。
- Client ID(アプリケーション ID)
- スコープ(Scope)の定義
今回は アクセストークンの形式 v2.0 を使用する想定のため、aud(audience)の値は Client ID と同じになります。
補足:トークン形式による
audの違い
- v2.0 形式:
aud= Client ID(例:12345678-1234-1234-1234-123456789abc)- v1.0 形式:
aud= Application ID URI(例:api://mcp-serverまたはhttps://contoso.com/api)トークン形式はアプリ登録の「マニフェスト」で
accessTokenAcceptedVersionの値により決定されます。
MCP サーバー側では、これらの値を検証時に使用します。
関連ドキュメント:
3.3 スコープのチェック(権限検証)
署名とクレームが正しくても、そのトークンに必要な権限があるかを確認する必要があります。
これが スコープ(scp)のチェックです。
scopes = claims.get("scp", "").split()
scp クレームには、そのトークンに付与されているアクセス許可がスペース区切りで格納されています。
例:"user.read files.write"
MCP サーバーは以下を確認します。
| チェック項目 | 確認内容 |
|---|---|
scp |
必要なアクセス許可(スコープ)がすべて含まれているか |
必須スコープが指定されている場合は、それらがすべて含まれているかをチェックします。
if not required.issubset(present):
raise AuthenticationError("missing_required_scopes")
これにより、
- トークンは正しい
- しかし権限が不足している
というケースを 403 Forbidden として適切に扱えます。
3.4 検証の全体フロー
クライアント
↓ Authorization: Bearer <JWT>
MCP サーバー
↓
① 署名検証(JWKS 取得 → 公開鍵で検証)
② iss / aud / exp / nbf チェック
③ scope(scp)チェック
↓
OK → API 実行
NG → 401 / 403
4. MCP サーバーでの Microsoft Entra ID 認証の実装について
いよいよ MCP サーバー側での実装について触れていきます。
今回は MCP サーバー側でアクセストークン検証を実装する前提 で説明を進めます。
ただし実運用では、必ずしも MCP サーバー側で検証処理を実装する必要はありません。
たとえば Azure 環境では、以下のような選択肢があります。
- Azure API Management を前段に配置してトークン検証を行う
- App Service 認証(Easy Auth) を利用して MCP サーバー側の実装を不要にする
このように「ゲートウェイ層で認証を終わらせる」設計も可能です。
今回は学習目的のため、あえて MCP サーバー内部でトークン検証を実装する方法 を選択し、仕組みを正しく理解することを目的とします。
関連ドキュメント:
4.1 実装対象ファイル
主に確認するファイルはこちらです。
-
src/auth/entra_auth_provider.py
https://github.com/shmiki-microsoft/entra-id-protected-mcp-server/blob/main/src/auth/entra_auth_provider.py#L46
このファイルでは、次の処理をまとめて実装しています。
- トークンの抽出
- JWKS の取得
- JWT 署名の検証
- クレーム(claims)の検査
- ユーザーコンテキストの生成
4.2 FastMCP のミドルウェアを拡張
この MCP サーバーは FastMCP を利用して実装しています。
FastMCP は認証用のミドルウェアクラスを提供しており、
今回はそのミドルウェアクラスを継承して、
EntraIDAuthProvider
という Microsoft Entra ID 専用のクラスを独自に作成しています。
※ FastMCP 公式にも Azure 連携の実装例はありますが、今回は勉強が目的なのであえて本リポジトリでは独自実装としています。
関連ドキュメント:
4.3 認証処理の流れ
FastMCP 側の処理フローは次の通りです。
-
Authorizationヘッダーから Bearer トークンを抽出 -
EntraIDAuthProvider.verify_token()を呼び出す - 成功すればリクエスト処理へ進む
- 失敗すれば 401 / 403 を返す
つまり、JWT の検証ロジックはすべて verify_token に集約されています。
4.4 初期化時:JWKS の取得
EntraIDAuthProvider は初期化時に、Microsoft Entra ID の公開鍵(JWKS)を取得します。
def __init__(
self,
tenant_id: str,
audience: str,
required_scopes: list[str] | None = None,
*,
jwks_timeout: float = 5.0,
jwks_max_retries: int = 3,
jwks_refresh_interval_seconds: int = 3600,
):
初期化時に設定している重要な値は以下です。
-
issuer(期待するトークン発行元) -
audience(この MCP サーバー向けのトークンかどうか) -
jwks_url(公開鍵の取得先)
self.issuer = f"https://login.microsoftonline.com/{tenant_id}/v2.0"
self.jwks_url = (
f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
)
そして、JWKS を事前取得します。
response = requests.get(self.jwks_url, timeout=5)
response.raise_for_status()
self._jwks = response.json()
ここで取得した公開鍵セットを使って、後続の JWT 検証を行います。
4.5 verify_token:JWT の検証
トークン検証の本体が verify_token メソッドです。
claims = jwt.decode(
token,
self._jwks,
algorithms=["RS256"],
audience=self.audience,
issuer=self.issuer,
)
この実装では python-jose を利用しています。
jwt.decode() に以下を渡すことで:
- JWKS(公開鍵)
- 期待する
aud - 期待する
iss - 使用するアルゴリズム(RS256)
をまとめて検証できます。
python-jose 側で次のチェックを自動的に実施してくれます。
- 署名検証
- 有効期限(
exp) - issuer チェック
- audience チェック
-
nbfチェック
検証が成功すれば、デコード済みの claims(ペイロード)が返ります。
関連ドキュメント:
4.6 スコープの検査
次に、scp(スコープ)の確認を行います。
scopes = claims.get("scp", "").split()
必須スコープが指定されている場合は、すべて含まれているかをチェックします。
if not required.issubset(present):
raise AuthenticationError("missing_required_scopes")
これにより、
- トークンは正しい
- しかし権限が不足している
というケースを 403 として扱えます。
4.7 AccessToken の生成
検証が成功すると、FastMCP 互換の AccessToken を返却します。
return AccessToken(
token=token,
claims=claims,
scopes=scopes,
client_id=claims.get("azp") or claims.get("appid"),
)
ここで claims が ユーザーコンテキスト として扱われ、
以降の MCP サーバー処理で利用可能になります。
4.8 エラーハンドリング
JWT 検証エラーは JWTError として発生します。
- 期限切れ →
access_token_expired - issuer 不一致 →
invalid_issuer - audience 不一致 →
invalid_audience - その他 →
invalid_access_token
といった形で AuthenticationError に変換しています。
これにより:
- 検証成功 → 処理継続
- 検証失敗 → 401 / 403
という明確な制御が可能になります。
4.9 ここまでのまとめ
この実装でやっていることは、突き詰めると次の 3 つです。
- Entra ID の公開鍵を取得する
- JWT の署名とクレームを検証する
-
claimsをアプリケーションのユーザーコンテキストへ変換する
「MCP サーバーに Entra ID 認証を実装する」とは、
この検証ロジックをアプリケーション側で正しく組み込むことを意味します。
より深く仕組みを理解したい方は、
verify_token 周辺にブレークポイントを打ち、実際の JWT を流して挙動を確認してみると理解が一段と深まります。
5. 発展的なトピック
ここからは、より実践的な認証・認可の実装について解説します。
-
RBAC(ロールベースアクセス制御) -
rolesクレームを使った権限管理 - OBO(On-Behalf-Of)フロー - MCP サーバーが他の API を呼び出す仕組み
6. Role クレームを使った RBAC
6.1 RBAC とは?
RBAC(Role-Based Access Control) は、ユーザーに割り当てられた「ロール(役割)」に基づいて、
アクセス権限を制御する仕組みです。
例えば:
- Admin → すべてのデータにアクセス可能
- Auditor → 監査用データのみ閲覧可能
- User → 基本的な情報のみアクセス可能
このように、ロールごとに操作範囲を制限することで、
きめ細かいアクセス制御を実現します。
関連ドキュメント:
6.2 Entra ID での App Roles 設定
Microsoft Entra ID では、App Roles という機能を使ってロールを定義できます。
6.2.1 App Roles の設定手順
- Azure Portal でアプリ登録を開く
- App roles メニューから新しいロールを作成
- 以下の情報を設定:
-
Display name:
Admin,Auditor,Userなど -
Allowed member types:
Users/GroupsまたはApplications -
Value:
Admin,Auditor,User(トークンに含まれる値) - Description: ロールの説明
-
Display name:
これを Microsoft Entra ID 側で認証対象のユーザーやグループに割り当てて使用します。
割り当てられた情報は後述の roles クレームの値に含まれます。
関連ドキュメント:
6.2.2 トークン内での表現
設定した App Roles は、JWT の roles クレームとして含まれます。
{
"aud": "12345678-1234-1234-1234-123456789abc",
"iss": "https://login.microsoftonline.com/{tenant-id}/v2.0",
"roles": ["Admin", "Auditor"],
"scp": "access_as_user",
"sub": "user-id",
"exp": 1735689600
}
6.3 MCP サーバー側での RBAC 実装
主に確認するファイルはこちらです。
-
src/tools/role_based_info.py
https://github.com/shmiki-microsoft/entra-id-protected-mcp-server/blob/main/src/tools/role_based_info.py
6.3.1 実装パターン
RBAC の実装の例として、今回は 2 つのパターンを実装しました。
パターン 1:ツールレベルでの制御
特定のツール自体を、特定のロールを持つユーザーのみが使えるようにする。
@mcp.tool()
async def get_sensitive_data(ctx: RequestContext) -> str:
"""機密データ取得(Admin のみ)"""
# roles クレームを取得
user_roles = ctx.access_token.claims.get("roles", [])
# Admin ロールがなければ拒否
if "Admin" not in user_roles:
raise PermissionError("Admin role required")
return "機密データ: ..."
パターン 2:レスポンス内容の制御
同じツールでも、ロールに応じて返す情報を変える。
@mcp.tool()
async def get_role_based_information(ctx: RequestContext) -> str:
"""ロールに基づいた情報取得"""
user_roles = ctx.access_token.claims.get("roles", [])
# 公開情報(全員)
info = ["公開情報: このサーバーは稼働中です"]
# User ロール以上
if "User" in user_roles:
info.append("基本情報: ユーザー数は 1,234 人です")
# Auditor ロール以上
if "Auditor" in user_roles:
info.append("監査情報: 本日のログイン回数は 567 回です")
# Admin ロールのみ
if "Admin" in user_roles:
info.append("機密情報: システムキーは ABC-123-XYZ です")
return "\n".join(info)
7. OBO(On-Behalf-Of)フロー
7.1 OBO とは?
OBO(On-Behalf-Of) は、「ユーザーの代わりに」別の API を呼び出すための仕組みです。
7.1.1 なぜ OBO が必要なのか?
MCP サーバーが以下のような処理を行う場合に必要になります。
- Microsoft Graph API を呼び出してユーザー情報を取得
- Azure Resource Manager を呼び出して Azure リソースを操作
- 他の 保護された API にアクセス
これらの API も OAuth で保護されているため、
MCP サーバーは「ユーザーの権限」でアクセスする必要があります。
OBO は、MCP サーバーがすでに受け取っているアクセストークンを使って、
Microsoft Graph API や Azure Resource Manager など別の API を呼び出すための
アクセストークンを Entra ID から取得する仕組みです。
関連ドキュメント:
7.1.2 トークンが使い回せない理由
イメージとしてはこんな感じのことが起こっています。
アクセストークンは、特定の API(audience)に対してのみ有効になるよう設計されています。
このため、MCP サーバーがエージェントから受け取ったアクセストークン(aud = MCP サーバーの Client ID)では、
Microsoft Graph API(aud = https://graph.microsoft.com)を呼び出すことができません。
つまり、トークンの使い回しができない仕組みになっています。
そこで、MCP サーバーは以下のように動作します。
- エージェントから MCP サーバー用のトークン A を受け取る
- トークン A を使って、Entra ID に「Graph API 用のトークン B をください」と要求(OBO)
- 取得したトークン B を使って、Graph API を呼び出す
この仕組みが OBO(On-Behalf-Of) です。
7.2 OBO の仕組み
7.2.1 通常のフロー(OBO なし)
Client → アクセストークン A を取得
Client → MCP サーバーにトークン A を提示
MCP サーバー → トークン A を検証
MCP サーバー → 処理を実行
7.2.2 OBO フロー
Client → アクセストークン A を取得(MCP サーバー用)
Client → MCP サーバーにトークン A を提示
MCP サーバー → トークン A を検証
MCP サーバー → Entra ID に「トークン A を使って、Graph 用のトークン B をください」と要求(OBO)
Entra ID → トークン B を発行
MCP サーバー → トークン B を使って Graph API を呼び出し
Graph API → データを返却
MCP サーバー → Client にレスポンスを返す
7.2.3 シーケンス図
7.3 OBO の実装
主に確認するファイルはこちらです。
-
src/auth/obo_client.py
https://github.com/shmiki-microsoft/entra-id-protected-mcp-server/blob/main/src/auth/obo_client.py -
src/tools/graph_user.py
https://github.com/shmiki-microsoft/entra-id-protected-mcp-server/blob/main/src/tools/graph_user.py -
src/tools/azure_vm.py
https://github.com/shmiki-microsoft/entra-id-protected-mcp-server/blob/main/src/tools/azure_vm.py
7.3.1 MSAL を使った OBO 実装
OBO の実装には今回 MSAL(Microsoft Authentication Library) を使用します。
理由は私が慣れているライブラリだからです。
(勉強目的という言い訳で深く考えていないですw)
もちろん他のライブラリを使って実装いただいてもよいです。
from msal import ConfidentialClientApplication
class OBOClient:
def __init__(self, client_id: str, client_secret: str, tenant_id: str):
self.client_id = client_id
self.authority = f"https://login.microsoftonline.com/{tenant_id}"
# MSAL の ConfidentialClientApplication を初期化
self.app = ConfidentialClientApplication(
client_id=client_id,
client_credential=client_secret,
authority=self.authority
)
async def get_token_for_graph(self, user_assertion: str) -> str:
"""Graph API 用のトークンを OBO で取得"""
scopes = ["https://graph.microsoft.com/.default"]
result = self.app.acquire_token_on_behalf_of(
user_assertion=user_assertion,
scopes=scopes
)
if "access_token" in result:
return result["access_token"]
else:
raise Exception(f"OBO failed: {result.get('error_description')}")
関連ドキュメント:
7.3.2 Graph API 呼び出しの実装
OBO で取得したトークンを使って、Graph API を呼び出します。
import httpx
@mcp.tool()
async def get_my_profile(ctx: RequestContext) -> dict:
"""Graph API でユーザープロフィールを取得"""
# 元のアクセストークン(MCP 用)を取得
original_token = ctx.access_token.token
# OBO で Graph 用トークンを取得
obo_client = OBOClient(
client_id=settings.CLIENT_ID,
client_secret=settings.CLIENT_SECRET,
tenant_id=settings.TENANT_ID
)
graph_token = await obo_client.get_token_for_graph(original_token)
# Graph API を呼び出し
async with httpx.AsyncClient() as client:
response = await client.get(
"https://graph.microsoft.com/v1.0/me",
headers={"Authorization": f"Bearer {graph_token}"}
)
response.raise_for_status()
return response.json()
関連ドキュメント:
7.3.3 Azure SDK を使った実装
Azure Resource Manager を呼び出す場合は、Azure SDK を使うこともできます。
from azure.identity import OnBehalfOfCredential
from azure.mgmt.compute import ComputeManagementClient
@mcp.tool()
async def list_azure_vms(ctx: RequestContext) -> list[dict]:
"""Azure VM 一覧を取得"""
# OBO 用の認証情報を作成
credential = OnBehalfOfCredential(
tenant_id=settings.TENANT_ID,
client_id=settings.CLIENT_ID,
client_secret=settings.CLIENT_SECRET,
user_assertion=ctx.access_token.token
)
# Compute Management Client を初期化
compute_client = ComputeManagementClient(
credential=credential,
subscription_id=settings.AZURE_SUBSCRIPTION_ID
)
# VM 一覧を取得
vms = []
for vm in compute_client.virtual_machines.list_all():
vms.append({
"name": vm.name,
"location": vm.location,
"vm_size": vm.hardware_profile.vm_size
})
return vms
関連ドキュメント:
7.4 OBO に必要な Entra ID の設定
OBO を使用するには、Entra ID で以下の設定が必要です。
7.4.1 クライアントシークレットの作成
MCP サーバーのアプリ登録で、クライアントシークレットを作成します。
- Azure Portal → アプリ登録 → 証明書とシークレット
- 新しいクライアントシークレット を作成
- 生成されたシークレットを安全に保管
重要
クライアントシークレットは一度しか表示されないため、必ず安全な場所に保存してください。
実運用では、環境変数や Azure Key Vault での管理、またはシークレットレス(マネージド ID とフェデレーション ID 資格情報)の利用を推奨します。
関連ドキュメント:
7.4.2 API アクセス許可の追加
MCP サーバーが呼び出す API(例:Microsoft Graph)へのアクセス許可を追加します。
- Azure Portal → アプリ登録 → API のアクセス許可
- アクセス許可の追加 をクリック
- Microsoft Graph を選択
-
委任されたアクセス許可 から必要なスコープを選択(例:
User.Read) - 管理者の同意を与える
注意
OBO で使用するアクセス許可は「委任されたアクセス許可」である必要があります。
「アプリケーションの許可」では OBO は機能しません。
関連ドキュメント:
おまけ:条件付きアクセスと MCP サーバーの関係
ここまで、MCP サーバー側での認証実装について解説してきましたが、
最後に 条件付きアクセス(Conditional Access) との関係について触れておきます。
条件付きアクセスとは?
条件付きアクセス は、Microsoft Entra ID が提供するセキュリティ機能で、
アクセスを許可する前に特定の条件をチェックする仕組みです。
例えば:
- デバイスの状態: マネージド デバイスからのアクセスのみ許可
- 場所: 特定の IP アドレスや国からのアクセスのみ許可
- リスクレベル: サインインリスクが高い場合は MFA を要求
- アプリケーション: 特定のアプリケーションへのアクセス時のみ制限を適用
関連ドキュメント:
条件付きアクセスが評価されるタイミング
重要なポイントは、条件付きアクセスはトークン発行時に評価される ということです。
ユーザー
↓
① 認証リクエスト
↓
Entra ID
↓
② 条件付きアクセスの評価 ← ここで評価される!
├─ デバイスの状態チェック
├─ 場所のチェック
├─ リスクレベルのチェック
└─ MFA の要求
↓
③ アクセストークン発行
↓
クライアント → MCP サーバー
つまり、条件付きアクセスの評価は Entra ID 側で完結 します。
MCP サーバーが関わるのは限定的
MCP サーバーの視点から見ると:
MCP サーバーがすること
✅ 発行されたトークンを検証する
- 署名の確認
- クレーム(aud, iss, exp など)の確認
- スコープの確認
MCP サーバーがしないこと
❌ 条件付きアクセスの評価
- デバイスの状態チェック
- 場所のチェック
- リスクレベルの評価
これらは既に トークン発行前に Entra ID が評価済み です。
例外:On-Behalf-Of(OBO)フローの場合
ただし、On-Behalf-Of(OBO)フローを利用する場合は少し事情が異なります。
この場合、MCP サーバーが Entra ID に対して 新しいアクセストークンの発行を要求するタイミング が存在します。
そのため、
MCP サーバー → Entra ID にトークン発行を依頼するタイミングで、
再度 条件付きアクセスの評価が実施される
可能性があります。
つまり、
- 通常の API 呼び出し時 → 条件付きアクセスはすでに評価済み
- OBO による再発行時 → その発行処理の中で評価される
という整理になります。
まとめ
- 条件付きアクセスは トークン発行時に Entra ID が評価する
- MCP サーバーは 発行済みトークンを検証する役割
- OBO フローでは、再発行時に改めて評価される可能性がある
役割を正しく分離して理解することが、設計をシンプルに保つポイントです。
8. 最後に振り返り
この記事では、MCP サーバーに Microsoft Entra ID 認証を実装する方法を、
OAuth の概念から具体的な実装まで解説しました。
8.1 重要なポイント
8.1.1 OAuth の役割分担
- Entra ID → アクセストークンを発行
- Client → トークンを取得して提示
- MCP サーバー → トークンを検証して判断
8.1.2 JWT 検証の 3 ステップ
- 署名の検証 - JWKS を使った改ざんチェック
- クレームの検証 - aud, iss, exp, nbf のチェック
- スコープの検証 - 必要な権限があるかチェック
8.1.3 RBAC のポイント
-
rolesクレームを使ったロールベースのアクセス制御 - ツールレベル、またはレスポンス内容レベルで制御可能
- 最小権限の原則を守り、デフォルトは制限する
8.1.4 OBO のポイント
- MCP サーバーが他の API を「ユーザーの代わりに」呼び出す仕組み
- MSAL の
acquire_token_on_behalf_ofを使って実装 - クライアントシークレットの安全な管理が必須
8.2 次のステップ
実際に動かしてみたい方は、以下のリポジトリを参照してください。
README に沿ってセットアップすることで、
本記事で解説した内容を実際に試すことができます。
最後までお読みいただき、ありがとうございました。
この記事が、MCP サーバーにおける Microsoft Entra ID 認証の理解に少しでも役立てば幸いです。

