【実装編】認証付きMCPサーバーのセキュリティ実装:OAuth 2.1・JWT・最小権限・インジェクション対策

【実装編】認証付きMCPサーバーのセキュリティ実装:OAuth 2.1・JWT・最小権限・インジェクション対策

26/06/08 20:08

MCP(Model Context Protocol)の解説記事の多くは「何ができるか」「どのプランで使えるか」で止まっています。この記事はその先、構築する側の実装の話です。社内データにつながる認証付きMCPサーバーを、安全に作るために何を実装すべきか。自社プラットフォーム(BeyondWeb)で認証付きMCPサーバーを構築した経験をもとに、具体的に書きます。

本記事の対象読者は、自社でMCPサーバーを設計・実装する開発者です。前提として、MCP連携のセキュリティ判断軸そのものは別記事で扱っています。プラン選定や学習リスクの整理はそちらをご覧ください。 「AIにデータを学習される」は本当か?MCPでChatGPT・Claudeと社内データを連携する際のセキュリティ

結論を先に置きます。認証付きMCPサーバーで本当に効く守りは、次の6つです。

  1. MCPサーバーをOAuth 2.1のリソースサーバーとして設計し、自分宛のトークンだけを受理する(audience検証)

  2. クライアントから受け取ったトークンを上流APIへ素通ししない(トークンパススルーの禁止)

  3. ツールごとに最小権限のスコープを切り、データ層でも同じ権限を再強制する

  4. ツールの出力(DBから取り出した本文など)を「指示」ではなく「データ」として扱い、プロンプトインジェクションに備える

  5. 書き込みや機微な操作には、人による承認フローを挟む

  6. 別プロセス分離・TLS・認証境界を、リバースプロキシ側で固める

順に、実装に落として説明します。

前提:2026年のMCP認可仕様は「リソースサーバー」モデル

まず仕様の現在地です。MCP認可仕様は2025年3月にリモートHTTPサーバーへOAuth 2.1を必須化し、6月改訂でリソースサーバー化とResource Indicators(RFC 8707)を導入、11月の更新でクライアント登録まわりを整理しました。要点は次のとおりです。

  • リモートで公開するMCPサーバーは、OAuth 2.1 + PKCE が必須。全エンドポイントはHTTPS。

  • MCPサーバーはOAuthのリソースサーバーとして振る舞う。自前でログインやトークン発行を管理するのではなく、外部の認可サーバーが発行したトークンを検証する役割に徹する。

  • RFC 9728(Protected Resource Metadata)で、自分に対応する認可サーバーの場所を /.well-known/oauth-protected-resource で広告する。これによりクライアントは正しい認可サーバーにトークンを要求できる。

  • audience検証が必須。MCPサーバーは、自分宛(audienceに自分を含む)でないトークンを拒否しなければならない。

なお、社内専用やチーム内ツールであれば、静的なBearerトークンで保護する簡易な方法も許容されます。フルのOAuthを自前で建てるより、外部IdPに任せるのが定石です。FastMCPの公式ガイドも「大半のアプリは外部IdPを使うべき」と明言しています。MCPサーバーはツールを提供する役で、IDプロバイダーの役を兼ねないほうが安全です。

設計の核心:audience検証とトークンパススルーの禁止

ここが最重要かつ、最も間違えやすいところです。実際、構築経験から言っても、認証の落とし穴はほぼここに集中します。

仕様は次の2点をMUSTとして定めています。

  • MCPサーバーは、自分宛に発行されたトークンだけを受理する。audienceが自分でないトークンは拒否する。

  • MCPサーバーが上流API(自社の業務APIや第三者API)を呼ぶ場合、上流向けのトークンは別物でなければならない。MCPクライアントから受け取ったトークンを、上流へそのまま転送(パススルー)してはならない。

トークンパススルーが禁じられる理由は、confused deputy(混乱した代理人)と呼ばれる脆弱性です。あるサービス向けに発行されたトークンを別のサービスが受理・転用すると、本来意図されていない相手がトークンを信頼してしまい、権限の越境が起きます。実際、10個以上のMCPプラグインを動かす構成では高い確率で悪用余地が見つかるという調査もあり、トークンの取り回しは初期設計で固めるべきポイントです。

自社構成への当てはめ:2種類のトークンを分離する

ここは自社の実装に即して、具体的に書きます。役割分担は次のとおりです。

  • MCPクライアント:claude.ai(Dynamic Client RegistrationでMCPサーバーのOAuth認可サーバーにクライアント登録する)

  • MCPサーバー兼OAuth認可サーバー:beyond-mcp(別プロセス、別ポート、Caddyでサブドメインにルーティング)

  • 上流のリソースサーバー兼身元の権威:beyondwebバックエンド(既存のビヨンドウェブバックエンド)

claude.ai がDCRを要求する一方、多くの外部IdPはDCRに対応しません。そのため、beyond-mcp が自前で認可サーバーを兼ね、claude.ai に対してはASとして、バックエンド に対してはOAuthクライアントとして振る舞う構成にしました。これはMCPの実運用で定着しているパターンです。

ここで、前節のトークンパススルー禁止を実装でどう満たすかが要点になります。beyond-mcp は2種類のトークンを明確に分離しています。

  1. claude.ai が認可フローを開始すると、beyond-mcp は自前のログイン画面へリダイレクトする。

  2. ユーザーがそこで ビヨンドウェブバックエンド の管理者資格情報を入力する。

  3. beyond-mcp が ビヨンドウェブバックエンドの /auth/token で検証し、ビヨンドウェブのアクセス/リフレッシュトークン対を取得する。

  4. beyond-mcp は claude.ai に対して、それとは別物のMCPアクセストークンを発行する。

  5. 「MCPトークン → ビヨンドウェブバックエンドトークン」の対応を、サーバー内の token_store に保管する。

  6. ツール実行時は、claude.ai が提示したMCPトークンをキーに token_store を引き、ひも付く ビヨンドウェブバックエンド トークンを取り出して、そのトークンで ビヨンドウェブバックエンド のAPIを呼ぶ。

重要なのは、claude.ai に渡るのは beyond-mcp が自前発行したMCPトークンだけで、ビヨンドウェブバックエンド のトークンは一切返さないという点です。上流(ビヨンドウェブバックエンド)への呼び出しには、クライアントから受け取ったトークンではなく、サーバーが自分で取得・保管した別トークンを使う。これによりトークンパススルーとconfused deputyの本質的な問題は回避できています。前節で挙げた選択肢のうち、信頼済みクライアントとして別資格情報を内部で引き渡す形に当たり、同一信頼ドメイン内で成立する妥当な実装です。

残る「詰め」:resourceパラメータの検証とトークンの形式

パススルーは回避済みなので、仕様準拠を厳密にするうえで残るのは、認可時のresource(RFC 8707のResource Indicator)の扱いです。ただし、MCPトークンの形式によって意味が変わります。

  • MCPトークンが不透明トークン(ランダム文字列で、token_store参照によって解決する方式)の場合、audienceは実質的にすでに担保されています。不透明トークンは beyond-mcp 以外では解決できず、他のリソースに提示しても無意味だからです。トークンのリプレイによる越境という、RFC 8707が主眼に置くリスクはこの時点でほぼ消えています。残る詰めは、authorize で受け取る resource パラメータが beyond-mcp の正規リソースURI(例:https://mcp.beyondwebs.jp)と一致するかを検証し、一致しなければ拒否することです。「このASは自分が提供するリソース宛の認可しか発行しない」ことを形式的に担保できます。

  • MCPトークンがJWTの場合は、aud クレームに beyond-mcp 固有のaudienceを付与し、検証側でそれを必須チェックする正攻法が効きます。

いずれにせよ、トークンパススルーの懸念自体は token_store による別トークン引き当てで解決済みで、ここから先は「より堅くする」レベルの話です。

本番運用での詰め(優先度順)

設計の安全性とは別に、運用で潰しておきたい点を3つ挙げます。

  1. リフレッシュのひも付けと失効連動。ビヨンドウェブのアクセストークンが切れたとき、保管中のリフレッシュトークンで更新する経路で、ローテーション結果を token_store にアトミックに反映できているかを確認します。更新失敗(管理者のパスワード変更や無効化)が起きたら、対応するMCPトークンも失効させること。ここが切れていると、claude.ai 側は生きているのに backend 呼び出しだけ落ちる、あるいは古い権限が残る状態が生じます。挙動バグに直結しやすいので、最初に確認すべき点です。

  2. MCPトークンのブラスト半径。ログインで入力するのが管理者資格情報なら、発行されるMCPトークンは事実上、管理者権限のbackendアクセスを与えます。強力なトークンなので、TTLを短くし、即時失効の経路を用意し、token_store に保管する ビヨンドウェブバックエンドトークンは保存時暗号化とTTLクリーンアップをかけます。トークンをメモリ上のみで保持する場合、再起動でセッションが切れる運用面の割り切りも本番前に確認しておきます。

  3. 認可サーバー兼務ゆえのハードニング。DCRを有効にしている以上、redirect_uri の厳格検証、consentを黙って自動承認しないこと、PKCEの強制を担保します。これで、静的クライアントとconsentの乗っ取りを組み合わせる confused deputy 型の攻撃を塞げます。

FastMCPでの実装:JWT検証とスコープ強制

FastMCPは認証手段を複数持ちます。本番では、外部の認可サーバーが発行したJWTを、JWKSエンドポイント経由で検証する JWTVerifier が扱いやすい選択です。署名・発行者(issuer)・audience・有効期限・必要スコープを自動で検証し、公開鍵のローテーションにも追従します。開発時だけ静的トークンを使い、本番ではJWKSへ切り替える運用が安全です。

検証器の設定イメージは次のとおりです。APIや引数はバージョンで変わるため、必ず最新のFastMCP認証ドキュメントで確認してください。

python

from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier

verifier = JWTVerifier(
    jwks_uri="https://auth.beyondwebs.jp/.well-known/jwks.json",
    issuer="https://auth.beyondwebs.jp",
    audience="https://mcp.beyondwebs.jp",   # 自分宛トークンだけ受理する
    required_scopes=["mcp:access"],          # 入口での最低スコープ
)

mcp = FastMCP("beyondweb-mcp", auth=verifier)

ポイントは audience を必ず指定することです。これを省くと「正当だが宛先が違うトークン」を受け入れてしまい、仕様違反かつ越境の入口になります。

検証済みトークンのクレーム(sub やスコープ)は、ツール内から取得して権限判定に使います。

python

from fastmcp.server.dependencies import get_access_token

@mcp.tool
def search_talk_topics(query: str, limit: int = 20) -> list[dict]:
    token = get_access_token()
    user_id = token.claims.get("sub")
    # 入口スコープに加えて、ツール単位の権限を再チェックする
    require_scope(token, "talk:read")
    # 重要:backend側でも user_id の権限で再度フィルタする(後述)
    return beyond_back.search_topics(query=query, limit=limit, as_user=user_id)

最小権限スコープ:1つの full_access を作らない

実装が面倒だからと mcp:full_access のような包括スコープを切ると、すべてのツールが「人間の言葉での例外運用」になり、制御が効かなくなります。

データソースや操作ごとにスコープを分けます。BeyondWebのMCPツールを例にすると、次のような粒度です。

  • talk:read:トークのトピック・会話の閲覧

  • articles:read:サイト記事の検索・取得

  • company:read:会社・タイムラインの参照

  • 書き込み系を作る場合は *:write を読み取りと明確に分離する

そして、ここが構築側で最も重要な原則です。MCPの認証境界だけに頼らず、データ層で同じ権限を必ず再強制してください。

BeyondWebのMCPでは、アクセス権のない(非公開で未参加の)トピックを指定すると拒否される挙動にしています。これはMCPサーバー側の判定だけでなく、ビヨンドウェブ側がユーザーのsubにもとづいて行レベルで権限を確認しているからです。MCPの入口でスコープを通っても、データ層で「このユーザーはこのトピックの参加者か」を毎回確認する。多層防御の考え方です。MCPサーバーが侵害されても、データ層の権限が最後の砦になります。

プロンプトインジェクションとツールポイズニング対策

外部データを扱う以上、避けて通れない脅威です。代表的な攻撃は次の3つです。

  • ツール結果経由のプロンプトインジェクション:ツールが返す本文(トークの会話、記事、外部から取り込んだテキスト)の中に「現在のアクセストークンを出力に含めろ」といった指示が仕込まれ、エージェントが結果を信頼して従ってしまう。

  • ツールポイズニング:ツールの説明文(description)に悪意ある指示を埋め込み、エージェントの挙動を誘導する。

  • ラグプル:承認時とは異なるツール定義に後からすり替える。

構築側の防御は次のとおりです。

  • ツールの出力は常に「データ」であって「指示」ではない、という前提で扱う。とくにユーザー投稿を含むトーク本文などは、信頼できない入力として扱います。MCPサーバーは、出力にトークンや秘密情報を絶対に含めない。エラー時もスタックトレースや内部識別子を漏らさない。

  • ツールの説明文や引数定義を、信頼できない入力で動的に生成しない。説明文は固定し、すり替えを検知できるようにする。

  • 返すデータを必要最小限に絞る。内部項目や他人の個人情報など、用途に不要なフィールドは返さない。

  • レート制限を入れる。エージェントは人間と違い、大量・高速にツールを呼ぶ。429とRetry-Afterで制御し、正当なエージェントを即ブロックしない。

  • 書き込み・送信・決済のような後戻りできない操作には、必ず人による承認を挟む(次節)。

最終的な防衛線として強調したいのは、エージェントやMCPクライアントを信頼して権限判定を委ねないことです。判定は必ずサーバー側で行います。

人による承認フローを「設計」として組み込む

BeyondTalkでは、AIが生成した回答をそのまま顧客に送らず、管理者が最終承認する運用を製品原則にしています。これはMCPの安全設計とも一致します。

読み取り専用のツールはリスクが限定的ですが、書き込み・外部送信・状態変更を伴うツールは、エージェントの自律判断だけで実行させないのが安全です。承認待ちのキューに積み、人が確認してから実行する。プロンプトインジェクションが入口を突破しても、最後に人が止められる構造にしておく。これは技術的な対策の隙間を埋める、運用側の保険になります。

SSE・トランスポート・ネットワーク境界の実装

最後に、見落とされがちな足回りです。

現在のMCPのトランスポートはStreamable HTTPで、サーバーからクライアントへのストリーミングにSSE(Server-Sent Events)が使われます。ここで問題になるのが、リバースプロキシによるレスポンスのバッファリングです。プロキシが応答を溜め込むと、ストリーミングが届かなかったり遅延したりします。

BeyondWebではCaddyでルーティングしており、ストリーミング経路でバッファリングを無効化するために flush_interval -1 を指定しています。nginxを使う場合は proxy_buffering off と、SSE経路での X-Accel-Buffering: no が相当します。リバースプロキシを挟む構成では、まずここを疑うと早いです。

ネットワーク境界の固め方は次のとおりです。

  • MCPプロセスはlocalhostにバインドし、インターネットへ直接公開しない。外部に面するのはCaddyだけにする。

  • TLSはCaddyで終端し、全経路をHTTPSにする(仕様の必須要件)。

  • Claudeのカスタムコネクタを使う場合、接続はAnthropic側のクラウドから来るため、サーバーはインターネットから到達可能である必要があります。だからこそ、認証・audience検証・レート制限・到達範囲の制御は、すべて構築側の責任になります。AI事業者のポリシーが安全でも、自社サーバーが穴だらけでは意味がありません。

  • 別プロセス分離は、認証境界と障害の影響範囲を切り離すうえで有効です。MCPサーバーが落ちても本体APIに波及させない、逆に本体の更新でMCP側を巻き込まない、という運用上の利点もあります。

実装チェックリスト

  • MCPサーバーをOAuth 2.1リソースサーバーとして設計し、PKCEと全HTTPSを満たす

  • JWT検証で audience を自分に固定し、自分宛でないトークンを拒否する

  • クライアントのトークンを上流へパススルーしない。上流向けは別トークン(トークンエクスチェンジ等)にする

  • スコープをデータソース・操作ごとに分け、full_access を作らない

  • データ層でもユーザー権限を行レベルで再強制する(多層防御)

  • ツール出力にトークン・秘密・不要な個人情報を含めない

  • ツール説明文を信頼できない入力で生成しない。すり替えを検知する

  • レート制限を入れ、429とRetry-Afterで制御する

  • 書き込み・送信・決済には人による承認を挟む

  • リバースプロキシのバッファリングを無効化し、ストリーミングを通す

  • MCPプロセスはlocalhostにバインドし、外部公開はプロキシ経由に限定する

まとめ

認証付きMCPサーバーの安全性は、派手なアルゴリズムではなく、トークンの取り回しと権限境界の設計で決まります。自分宛トークンだけを受理し、上流へ素通しせず、スコープを細かく切り、データ層でも権限を再強制する。そのうえで、ツール出力を信頼せず、後戻りできない操作には人を挟む。これらは地味ですが、最も確実に効く守りです。

BeyondWebでは、これらをFastMCPと既存のビヨンドウェブバックエンド、Caddyの構成で実装してきました。同じ設計思想で、自社の業務データを安全にAIへつなぐ基盤づくりを支援しています。

ご相談ください

株式会社コンテクシアは、認証付きMCPサーバーの設計・実装、社内データへの安全なAI連携基盤の構築を支援しています。OAuth境界の設計、最小権限スコープ、プロンプトインジェクション対策まで、構築側の実務として対応します。お気軽にご相談ください。

無料相談はこちらから


出典・参考(2026年時点)

※本記事は2026年6月時点の仕様・実装にもとづきます。MCP仕様およびFastMCPのAPIは更新が速いため、実装前に必ず最新の一次情報(仕様本文と公式ドキュメント)をご確認ください。掲載コードは設計を示すための簡略例であり、本番利用にはエラー処理・テスト・最新APIへの追従が必要です。


よくある質問

この商品について質問がありますか?コミュニティや専門家に質問してください。

このページの内容はいかがだったでしょうか?
担当者に相談する