2025年12月17日水曜日

Role based JWT profileで保護したORDS REST APIにMCP Inspectorでアクセスする - Microsoft Entra ID編

Oracle REST Data ServicesのRESTモジュールをRole based JWT profileで保護し、そのREST APIにMCP Inspectorでアクセスしてみます。

記事「ORDSのRESTサービスとして作成したリモートMCPサーバーをMicrosoft Entra IDで認証する」で作成した作業環境を使用します。元記事はGoogle CloudのVMインスタンスを使用していますが、今回の作業はOracle CloudのComputeインスタンスで実施しました。Google CloudのVMインスタンス(Rocky Linux 10)との違いを、記事の最後に記載します。

Microsoft Entra IDについては記事「ORDSのRESTサービスとして作成したリモートMCPサーバーをMicrosoft Entra IDで認証する」と近い手順ですが、MCP Inspectorはシングル・ページ・アプリケーションである点が異なります。設定の違いは、以下の2点です。
  • シングル・ページ・アプリケーションかつパブリッククライアントフローを許可しています。そのため、クライアント・シークレットは作成していません。MCP InspectorとEntra ID間での通信なので確認できていませんが、PKCEが実行されているはずです。
  • アクセス・トークンに含めるrolesクレームのメンバーを、グループからアプリロールに変更しています。アプリロールにすることで、ORDS側のロールをIDではなく名前で設定できます。
以下より確認作業について記載します。

規定のディレクトリ管理アプリの登録を開き、新規登録を開始します。


名前ORDS MCPリダイレクトURIシングル・ページ・アプリケーションを選択し、 リダイレクトURIにMCP InspectorのRedirect URLを設定します。ポート番号6274が使用されている場合は、ポート番号が異なるかもしれません。

http://localhost:6274/oauth/callback


Redirect URLの値は、MCP InspectorのOAuth 2.0 Flow - Redirect URLに記載されています。


アプリORDS MCPが作成されます。ここに表示されているアプリケーション(クライアント)IDをメモしておきます。


このクライアントIDは、MCP InspectorのOAuth 2.0 Flow - Client IDに設定します。


アプリORDS MCP管理Authentication (Preview)を開き、設定タブを選択します。

パブリッククライアントフローを許可する有効に切り替え、保存します。


アプリORDS MCP管理APIの公開を開き、アプリケーションIDのURI追加します。


デフォルトで割り当てられるアプリケーションIDのURI保存します。

この値はORDS_SECURITY.CREATE_JWT_PROFILEを呼び出す際の引数p_audienceの値になります。また、oauth-protected-resourceの属性resourceに指定する値になります。


アプリORDS MCP概要エンドポイントを開きます。


OpenID ConnectメタデータドキュメントのURLをコピーします。

このURLで取得できるファイルを、nginxが返す/.well-known/openid-configurationとして配置します。


URLをブラウザで開き、属性jwks_uriの値をコピーします。

この値はORDS_SECURITY.CREATE_JWT_PROFILEの引数p_jwk_urlの値になります。


OpenID ConnectメタデータドキュメントのURLの末尾にある.well-known以下を除いた部分が、oauth-protected-resourceauthorization_serversの値になります。

あとはアクセス・トークンのissuerとなる値があれば、ORDS REST APIの保護に必要な値が集まるのですが、issuerについてはMicrosoft Entra IDのAdmin Consoleでの確認場所が不明でした。

ORDS RESTモジュールの保護はアプリロールで実施するため、アプリロールを作成します。

アプリORDS MCPの管理のアプリロールを開きます。

アプリロールの作成をクリックします。


表示名は「ORDS REST APIの呼び出し」とし、許可されたメンバーの種類ユーザーまたはグループを選択します。ORDSUsersとします。この値がアクセス・トークンのrolesクレームに含まれます。説明には「ORDS REST APIの呼び出し」と記述します。

このアプリロールを有効にしますか?をチェックします。

以上で適用をクリックします。


アプリロールが作成されます。


作成したアプリロールをユーザーに割り当てます。

アプリORDS MCP概要を開き、エンタープライズアプリケーションに移動します。


管理ユーザーとグループを開き、ユーザーまたはグループの追加を開始します。


ユーザー選択されていませんをクリックし、ユーザーを割り当てます。


アプリロールを割り当てるユーザー選択します。


ユーザーを選択したのち、割り当てを実行します。アプリロール「ORDS REST APIの呼び出し」はデフォルトで選択されているようです。


ユーザーにアプリロールが割り当てられました。確認するため、ユーザーを開きます。


アプリケーションを開きます。


選択したユーザーにアプリケーションORDS MCPのアプリロール「ORDS REST APIの呼び出し」が割り当たっていることを確認します。


Microsoft Entra IDでの設定は以上です。

以下のスクリプトを実行し、Oracle REST Data Servicesに権限oracle.example.mcpを作成(すでに存在する場合は再定義)します。ロールとしてORDSUsersを作成し、RESTモジュールsampleserverを保護します。
declare
    l_roles    owa.vc_arr;
    l_modules  owa.vc_arr;
    l_patterns owa.vc_arr;
begin
    ords.create_role(
        p_role_name => 'ORDSUsers'
    );
    l_modules(1) := 'sampleserver';
    l_roles(1)   := 'ORDSUsers';
    ords.define_privilege(
        p_privilege_name => 'oracle.example.mcp',
        p_label          => 'Priviledge for MCP',
        p_roles          => l_roles,
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/

issuerがわからないため、ORDS_SECURITY.CREATE_JWT_PROFILEの実行より先に、/.well-known/以下を設定します。

nginxを実行しているインスタンスに接続します。

rootユーザーで作業します。

sudo -s

[opc@apex ~]$ sudo -s

[root@apex opc]# 


nginxのドキュメント・ルートに移動します。

cd /usr/share/nginx/html

[root@apex html]# cd /usr/share/nginx/html

[root@apex html]# 


認証フローで参照されるファイルを配置するディレクトリを作成します。

mkdir -p .well-known/ords/apexdev/sampleserver

[root@apex html]# mkdir -p .well-known/ords/apexdev/sampleserver

[root@apex html]# 


リモートMCPサーバーのアクセスパスが/ords/apexdev/sampleserver/mcpなので、oauth-protected-resourceとして記述する内容は.well-known以下の.well-known/ords/apexdev/sampleserver/mcpに記述します。Oktaでは、このファイルの位置が.well-known以下の.well-known/oauth-protected-resource/ords/apexdev/sampleserver/mcpでした。

今回の実装ではサーバーが401 Unauthorizedを返すときにWWW-Authenticateヘッダーを返しません。そのため、oauth-protected-resourceの位置が定まらないのかもしれません。

ファイル.well-known/ords/apexdev/sampleserver/mcpを作成し、以下を記述します。
{
  "resource": "api://********-****-****-****-************",
  "authorization_servers": {
    ["https://login.microsoftonline.com/********-****-****-****-************/"]
} }
authorization_serversの設定で、JSON配列を{}で囲んでJSONオブジェクトにしています。構文的に間違っているように思いますが、こうしないとMCP Inspectorがauthorization_serversを認識しません。

OpenID Connectメタデータドキュメントをダウンロードし、.well-known以下に配置します。

curl -OL https://login.microsoftonline.com/*******-****-****-****-***********/v2.0/.well-known/openid-configuration
mv openid-configuration .well-known/

[root@apex html]# curl -OL https://login.microsoftonline.com/********-****-****-****-************/v2.0/.well-known/openid-configuration

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100  1965  100  1965    0     0   3286      0 --:--:-- --:--:-- --:--:--  3291

[root@apex html]# mv openid-configuration .well-known/

[root@apex html]# 


/etc/nginx/nginx.confを開き、ヘッダー情報がログに出力されるextended.logが有効になるように、access_logのコメントを変更します。

    #access_log  /var/log/nginx/access.log  main;

    access_log   /var/log/nginx/extended.log extended;

    #access_log   /var/log/nginx/body.log body;


nginxをリロードします。

nginx -s reload

[root@apex html]# nginx -s reload

[root@apex html]# 


extended.logの出力を監視します。

tail -f /var/log/nginx/extended.log

[root@apex html]# tail -f /var/log/nginx/extended.log 



MCP Inspectorを起動します。

npx @modelcontextprotocol/inspector

Transport TypeStreamable HTTPを選択し、URLにORDSのRESTモジュールとして実装されているサンプルのMCPサーバーのURLを設定します。

https://ホスト名/ords/apexdev/sampleserver/mcp

OAuth 2.0 FlowClient IDに、Microsoft Entra IDに作成したORDS MCPアプリケーション(クライアント)IDを設定します。

まだJWTプロファイルを作成していないためEntra IDのサインインが成功しても、ORDS REST APIは401 Unauthorizedを返します。そのため、ギブアップするまでサインインが繰り返されます。

Connectをクリックし、認証プロセスを開始します。


サインインのプロセスが開始します。サインインするアカウントを選択します。


Authenticatorを使うように構成しているアカウントなので、通知の送信を要求されます。


Authenticatorを操作します。


サインインの状態を維持しますか?は、はいいいえのどちらを選択してもかまいません。


アクセス許可の承諾を求められます。承諾をクリックします。


ユーザー認証に成功していると、extented.logにauthorizationヘッダーのBearerで与えられているアクセス・トークンが出力されます。ORDSは必ず401を返すため、何度もサインインを繰り返します。アクセス・トークンが取得できた時点で、MCP InspectorをCtrl+Cで中断します。


jwt.ioにアクセスし、アクセス・トークンをデコードします。


デコードされたアクセス・トークンの内容を確認します。

最初に、ORDS_SECURITY.CREATE_JWT_PROFILEの引数p_issuerの値になるiss属性を確認します。

iss属性は問題ありませんが、以下の2つの問題が見つかりました。
  • aud属性つまりaudienceがapi://で始まる値になっていない。
  • アクセス・トークンにrolesクレームが含まれていない。
Microsoft Entra IDでは、アクセス・トークンのaud属性(audience)をアプリケーション(クライアント)IDとするには、そのアプリケーションに作成したスコープをリクエストに含める必要があるようです。(Microsoft Entra IDについて、それほど詳しくはないので、違う可能性もあります。

そのため、JWTプロファイルの保護には使用しませんが、スコープを作成します。

アプリORDS MCP管理APIの公開を開き、Scopeの追加をクリックします。


スコープ名oracle.example.dummy.mcpとします。同意できるのはだれですか?管理者とユーザー管理者の同意の表示名Entra IDのaudience設定用管理者の同意の説明Entra IDのaudience設定用とします。とします。状態有効とします。

以上で、スコープの追加をクリックします。


追加されたスコープをコピーします。


このスコープはMCP InspectorOAuth 2.0 FlowScopeに指定します。


アクセス・トークンからissuerの値が取得できたので、ORDS_SECURITY.CREATE_JWT_PROFILEを実行します。引数p_role_claim_nameには/rolesを指定します。
begin
    ords_security.delete_jwt_profile;
    ords_security.create_jwt_profile(
        p_issuer => 'issの値'
        ,p_audience => 'audの値'
        ,p_jwk_url => 'jwks_uriの値'
        ,p_role_claim_name => '/roles'
    );
end;
/

ビューUSER_ORDS_JWT_PROFILEを検索し、設定内容を確認します。

select issuer,audience,jwk_url,role_claim_name from user_ords_jwt_profile


OAuth 2.0 Flowにaudienceを含むスコープが設定されていると、MCP InspectorでのOAuth2の認証が成功します。audienceが適切に選択されていると、アクセス・トークンにrolesクレームとアプリロールORDSUsersが含まれるようになります。


以上でRole based JWT profileで保護したORDS REST APIを、MCP Inspectorでアクセスできました。

Microsoft Entra IDではスコープの指定が必須であったため、ChatGPTのアプリとコネクターまたはClaude Desktopのカスタムコネクタとして使用するのは難しそうです。


Oracle Linux 10との違い


Oracle Linux 10ではデフォルトでfirewalldが実行されます。以下のようにfirewalldを無効化することもできますが、今回はhttpとhttpsへの接続許可を与えています。

[opc@apex ~]$ sudo systemctl stop firewalld

[opc@apex ~]$ sudo systemctl disable firewalld

Removed '/etc/systemd/system/multi-user.target.wants/firewalld.service'.

Removed '/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service'.

[opc@apex ~]$ 


以下、実行したコマンドです。

[opc@apex ~]$ sudo firewall-cmd --add-service=https

success

[opc@apex ~]$ sudo firewall-cmd --add-service=http

success

[opc@apex ~]$ sudo firewall-cmd --runtime-to-permanent

success

[opc@apex ~]$ sudo firewall-cmd --reload

success

[opc@apex ~]$ sudo firewall-cmd --list-all

public (default, active)

  target: default

  ingress-priority: 0

  egress-priority: 0

  icmp-block-inversion: no

  interfaces: enp0s5

  sources: 

  services: dhcpv6-client http https ssh

  ports: 

  protocols: 

  forward: yes

  masquerade: no

  forward-ports: 

  source-ports: 

  icmp-blocks: 

  rich rules: 

[opc@apex ~]$ 


Rocky Linux 10ではcertbotを含むリポジトリを追加するためにepel-releaseをインストールしました。Oracle CloudのComputeインスタンスのOracle Linux 10にはEPELのリポジトリが構成されていたため、以下のコマンドで有効化しています。

sudo dnf config-manager --enable ol10_u0_developer_EPEL

[opc@apex ~]$ sudo dnf config-manager --enable ol10_u0_developer_EPEL

[opc@apex ~]$


リポジトリol10_u0_developer_EPELはファイル/etc/yum.repos.d/oracle-epel-ol10.repoで定義されています。リポジトリ名にu0(baseurlにも0が含まれる)が含まれているため、Oracle Linux 10のリリースが10.1、10.2と上がっていくと名前が変わるかもしれません。

その他では、Computeインスタンス作成時にブート・ボリュームのサイズを100GBに変更しました。Oracle Cloudではブート・ボリュームのサイズを拡張しても、作成されたインスタンスのファイル・システムのサイズはそのままなので、ルート・ファイル・システムのサイズを拡張しています。この手順については、他に解説があると思うので割愛します。

今回の記事は以上になります。