0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCP OAuth2 Plugin + Entra IDの動作検証

0
Last updated at Posted at 2026-04-21

Kong GatewayのMCP OAuth2 Pluginは3.13まではサポートするトークン検証方法がIntrospectionしかなかった。
日本の企業でよく使われるEntra IDなんかはIntrospectionをサポートしていないため、MCP OAuth2 PluginはEntra IDと組み合わせて使うことができなかった。
しかし、MCP OAuth2 Plugin 3.14からはJWT検証もサポートされるようになったため、Entra IDと組み合わせて使うことができるようになった。
今回はこれを検証してみる。

前提知識

MCP OAuth2 Pluginを使った認可フローは以下のようになる。

アクセストークンはデフォルトで上流サービスに転送されない設計となっており、トークン盗聴や confused deputy 攻撃(権限を持つ仲介者が悪用されて不正操作される攻撃)を防止している。

各フェーズで使われる値の詳細は以下の通り。

  1. メタデータ取得

    • KongはProtected Resource Metadataに認可サーバーのIssuer URLを埋め込んで返す
    • 必要な値: Issuer URL (https://login.microsoftonline.com/{tenant_id}/v2.0)
  2. トークン取得

    • MCP ClientはEntra IDのトークンエンドポイントに client_id / client_secret / scope を送りJWTを取得する
    • 必要な値: テナントID、クライアントID、クライアントシークレット、スコープ
  3. JWT検証

    • JWKSエンドポイント (https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys) から公開鍵を取得し署名を検証
    • iss, aud, exp等も検証
  4. MCPサーバへ転送(フロー図 フェーズ3 — 有効時)

    • Kongはトークンを除去し、リクエストをMCPサーバーへ転送する
    • 必要な値: サービスURL(MCPサーバーのエンドポイント)

なお、フローの通りMCP OAuth2 Pluginは認可のみであり認証は行わず、トークンの取得はクライアント側で実施することになる。
ただConsumerとのマッピングは可能で、consumer_byで指定したConsumer情報とsubクレームを照合することもできる(今回は扱わない)。
MCP Proxy Pluginと組み合わせた場合、MCP OAuth2 PluginはMCP Proxy Pluginの前段で動作し、トークン検証後にMCPとAPIの変換を行う。

事前準備

以下は既にあるものとする。

  • Kong Konnect (CPとDP)
  • Entra IDテナントとアプリ登録
    • クライアントID / クライアントシークレットを控えておく
    • accessTokenAcceptedVersionは2に設定する(マニフェスト->AAD Graphアプリマニフェストで設定可能)
  • MCPサーバー(またはAPIサーバーを用意し、MCP Proxy PluginでMCPサーバー化する)
  • MCPクライアント

今回の検証ではDPとhttpbinをローカル環境で起動して検証した。
httpbinに対してはMCP Proxy Pluginを使ってMCPサーバー化する。
MCPクライアントはリクエスト・レスポンスの中身を可視化するためにcurlを利用した。

検証

検証手順としては以下となる。

  1. MCP OAuth2 Pluginの設定
    2.検証用のトークンの取得
  2. MCPリクエストの送信とレスポンスの確認

また最初に検証に使う環境変数を設定する。

# deck
export DECK_KONNECT_ADDR=https://us.api.konghq.com
export DECK_KONNECT_TOKEN=<your-konnect-token>
export DECK_KONNECT_CONTROL_PLANE_NAME=<your-control-plane-name>
# Entra ID
export ENTRA_TENANT_ID=<your-tenant-id>
export ENTRA_CLIENT_ID=<your-client-id>
export ENTRA_CLIENT_SECRET=<your-client-secret>
export ENTRA_APP_ID_URI=${ENTRA_CLIENT_ID}
export ENTRA_SCOPE=api://${ENTRA_APP_ID_URI}/.default
# MCP OAuth2 Plugin
export DECK_MCP_RESOURCE_URL=api://${ENTRA_APP_ID_URI}
export DECK_AUTHORIZATION_SERVER_URL=https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0
export DECK_JWKS_ENDPOINT_URL=https://login.microsoftonline.com/${ENTRA_TENANT_ID}/discovery/v2.0/keys

1. MCP OAuth2 Pluginの設定

以下のdeck用YAMLを用意して設定する。

deck.yaml
_format_version: "3.0"

services:
  - name: mcp-oauth-entraid-service
    protocol: http
    host: httpbin
    path: /anything
    port: 80
    routes:
      - name: mcp-oauth-entraid-route
        paths:
          - /mcp
          - /.well-known/oauth-protected-resource
        protocols:
          - http
        plugins:
          - name: ai-mcp-oauth2
            config:
              resource: ${{ env "DECK_MCP_RESOURCE_URL" }}
              authorization_servers:
                - ${{ env "DECK_AUTHORIZATION_SERVER_URL" }}
              jwks_endpoint: ${{ env "DECK_JWKS_ENDPOINT_URL" }}
              metadata_endpoint: /.well-known/oauth-protected-resource
              insecure_relaxed_audience_validation: true
          - name: ai-mcp-proxy
            config:
              mode: conversion-listener
              tools:
                - name: httpbin-anything
                  description: Send a GET request to httpbin /anything endpoint via MCP tools/call
                  method: GET

設定の意味は以下となる。

        paths:
          - /mcp
          - /.well-known/oauth-protected-resource

通常のRouteと異なり、最初の認可サーバの取得用に/.well-known/oauth-protected-resourceもルーティングしてあげる必要がある。
このパスを設定した上でMCP OAuth2 Pluginを有効にすることで、クライアントは最初に/.well-known/oauth-protected-resourceにアクセスして認可サーバのURLを取得することができるようになる。

              resource: ${{ env "DECK_MCP_RESOURCE_URL" }}
              authorization_servers:
                - ${{ env "DECK_AUTHORIZATION_SERVER_URL" }}
              jwks_endpoint: ${{ env "DECK_JWKS_ENDPOINT_URL" }}
              metadata_endpoint: /.well-known/oauth-protected-resource
              insecure_relaxed_audience_validation: true

各値の意味は以下となる。

  • resource: トークンのaudienceクレームと照合する値。Entra IDの場合はApplication ID URIを指定するのが一般的だが、検証の結果Client CredentialsフローではaudienceはGUID形式で返却されるため、insecure_relaxed_audience_validation: trueを設定してaudience検証をスキップするのが現実的な解となる。
  • authorization_servers: 認可サーバーのURL。トークンのissクレームと照合するために使用される。
  • jwks_endpoint: JWTの署名検証に使う公開鍵が置いてあるURL
  • metadata_endpoint: クライアントが認可サーバの情報を取得するためのエンドポイント。これを設定することでプラグインがこのパスをメタデータエンドポイントとして認識し、認証をスキップしてメタデータを返すようになる。
  • insecure_relaxed_audience_validation: trueに設定するとaudience検証をスキップする。Entra IDのClient CredentialsフローではaudienceがGUID形式で返却されるように見えているため、これをURL形式と照合することができない。今回はaudience検証をスキップするためにtrueに設定する。
              mode: conversion-listener
              tools:
                - name: httpbin-anything
                  description: Send a GET request to httpbin /anything endpoint via MCP tools/call
                  method: GET

MCP Proxy Pluginの設定。今回はhttpbinの/anythingエンドポイントにGETリクエストを送るツールを定義している。

上記のYAMLを作成したらKongに設定を反映する。

deck gateway sync ./deck.yaml

2. 検証用のトークンの取得

Client Credentialsフローでアクセストークンを取得する。

TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=${ENTRA_CLIENT_ID}" \
  -d "client_secret=${ENTRA_CLIENT_SECRET}" \
  -d "scope=${ENTRA_SCOPE}" \
  | jq -r '.access_token')

echo $TOKEN

取得したJWTのクレームを確認する場合は以下で確認できる(ペイロード部分をデコード)。

echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .

デフォルトのトークンの有効期限は1時間(3600秒)となっているため、検証が長引く場合は適宜トークンを再取得すること。

3. MCPリクエストの送信とレスポンスの確認

認可フローに従ってアクセスする。
まず最初に/.well-known/oauth-protected-resourceにアクセスして、Issuer URLが正しく返ってくることを確認する。

curl -s http://localhost:8000/.well-known/oauth-protected-resource | jq .
{
  "resource": "http://localhost:8000/mcp",
  "authorization_servers": [
    "https://login.microsoftonline.com/<your-tenant-id>/v2.0"
  ]
}

authorization_serversには先程トークンの取得で利用したEntra IDのテナントIDつきURLが返ってきている事が分かる。
ここではトークンは取得済みなので、本来トークンを取得する手順をスキップして次にMCPリクエストを送信してみる。

curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/mcp/aaa | jq .

問題なければ以下のような結果が返ってくるはずだ。

{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Connection": "keep-alive",
    "Host": "httpbin",
    "User-Agent": "curl/8.7.1",
    "X-Forwarded-Host": "localhost",
    "X-Forwarded-Path": "/mcp/aaa",
    "X-Forwarded-Prefix": "/mcp",
    "X-Kong-Request-Id": "1fef1576c67bf81ff92a003d2a263540"
  },
  "json": null,
  "method": "GET",
  "origin": "172.22.0.1",
  "url": "http://httpbin/anything/aaa"
}

試しに誤ったトークンを設定して実行する。

TOKEN=invalid
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8000/mcp/aaa | jq .

以下の結果となった。

{
  "message": "Invalid or inactive token"
}

またKong DPのログには以下のようなJWTの検証で失敗したことを示すエラーが出ているはずだ。

2026/04/21 01:16:17 [notice] 2915#0: *4554 [kong] auth.lua:841 [ai-mcp-oauth2] invalid token detected during JWKS verification: failed to decode JWT: invalid JWT format, client: 172.22.0.1, server: kong, request: "GET /mcp/aaa HTTP/1.1", host: "localhost:8000", request_id: "2650662e51a8f674bf2d2529e89e980a"
2026/04/21 01:16:17 [debug] 2915#0: *4554 [kong] response.lua:1051 [ai-mcp-oauth2] exiting with status code: 401 and message: Invalid or inactive token

上手くJWTの検証ができていることが確認できた。
これまではMCP+OAuth2の構成を検証したい時にEntra IDでは検証できないという課題があったが、MCP OAuth2 Plugin 3.14からはEntra IDもサポートされたため、より幅広い環境での検証が可能になったことが確認できた。

トラブルシューティング

accessTokenAcceptedVersionの設定漏れでトークン検証に失敗

accessTokenAcceptedVersionが2以外になっていると、以下のようなエラーが出てトークンが無効とされる。

2026/04/21 01:08:42 [notice] 2916#0: *4035 [kong] auth.lua:841 [ai-mcp-oauth2] invalid token detected during JWKS verification: token structure validation failed: invalid issuer (https://sts.windows.net/<your-tenant-id>/) was specified for access token, https://login.microsoftonline.com/<your-tenant-id>/v2.0 was expected, client: 172.22.0.1, server: kong, request: "GET /mcp HTTP/1.1", host: "localhost:8000", request_id: "d8629544d56354285c8bbb1fb14e761c"
2026/04/21 01:08:42 [debug] 2916#0: *4035 [kong] response.lua:1051 [ai-mcp-oauth2] exiting with status code: 401 and message: Invalid or inactive token

上記エラーが出た場合はaccessTokenAcceptedVersionが2になっているかを確認すること。

resourceにURLを設定してもaudience検証が失敗する

Kongのresourceにhttp://localhost:8000/mcpのようなURLを設定しても、以下のエラーが返ることがある。

{"message": "Invalid audience in token"}

Entra IDが発行するJWTのaudクレームはURLではなく、アプリ登録のApplication IDやApplication ID URIになる。Kongはaudをresourceの値と照合するため、URLのまま設定しても一致しない。
またGUIDをそのままresourceに設定しようとすると以下のバリデーションエラーが発生する。

validation error: missing host in url

KongのresourceフィールドはURL形式が必須のため、GUIDは直接設定できない。
Entra IDはデフォルトでapi://<client-id>というApplication ID URIを持つため、これが利用できるようにも見えるが、Entra IDのClient CredentialsフローではaudはGUID形式で返却され、スコープをapi://<client-id>/.defaultと指定してもaudはGUIDのままであり、api://形式にはならなかった。
これが返ってくるようであればMCP OAuth2 Pluginではaudが実質検証できない。
同様の状態になる場合は検証をスキップするためのinsecure_relaxed_audience_validation: trueを設定して本問題を回避すれば動作させることが可能となる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?