2025年12月23日火曜日

ORDS RESTモジュールとして作成したリモートMCPサーバーをMicrosoft Entra IDで保護する

ORDS RESTモジュールとして作成したリモートMCPサーバーをMicrosoft Entra IDで保護します。MCP Inspectorについては記事「Role based JWT profileで保護したORDS REST APIにMCP Inspectorでアクセスする - Microsoft Entra ID編」に記載した手順で認証できたのですが、ブラウザ版ChatGPTおよびClaude Desktopは不可でした。

以下より、ブラウザ版ChatGPTのアプリまたはClaude Desktopのカスタムコネクタとして、作成したORDS RESTモジュールをMicrosoft Entra IDのユーザーで認証してみます。

今回の設定では、アクセス・トークンをV2.0に強制します。また、別アプリケーションとしてクライアントを作成します。

手順としては、おおむね以下の記事と同じです。

SQLclのMCPサーバーのデータベース接続をMicrosoft Entra IDのOAuth2で認証する

SQLclのMCPサーバーのデータベース接続にTOKEN_AUTH=AZURE_INTERACTIVEの設定を使用する
https://apexugj.blogspot.com/2025/08/sqlcl-mcp-with-azure-interactive.html


Microsoft Entra IDの設定



接続先となるORDS RESTモジュールに対応するアプリケーションを作成します。

規定のディレクトリよりアプリの登録を開き、新規作成を実行します。


名前ORDS MCP2とします。リダイレクトURI省略します。リダイレクトURIはクライアントに対応するアプリケーションに作成します。

アプリケーションを登録します。


管理APIの公開を開き、Scopeの追加を実施します。

リモードMCPサーバーをMicrosoft Entra IDで認証する場合、リソース・サーバーは401 Unauthorizedのレスポンスを返すときにWWW-Authenticateヘッダーで、ここで作成するスコープを返す必要があります。


アプリケーションIDのURIとして、デフォルト値を設定します。

保存してから続けるをクリックします。


作成するスコープのスコープ名mcp:connectとします。同意できるのはだれですか?として管理者とユーザーを選択します。管理者の同意の表示名管理者の同意の説明ともに「Connect to My MCP Server」と記述します。

状態有効を選択し、スコープの追加をクリックします。


管理アプリロールを開き、アプリロールの作成をクリックします。

ロールによってORDS RESTモジュールを保護するので、ここで作成するアプリロールと同名のロールを、ORDSに作成しています。


表示名ORDS REST APIの呼び出し許可されたメンバーの種類両方(ユーザー/グループ+アプリケーション)を選択します。

ORDSUsers(これがORDSのロール名になります)、説明は「ORDS REST APIの呼び出し」と記述します。

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


アプリケーションORDS MCP2をEntra IDのユーザーに割り当てます。割り当てるユーザーに、作成したアプリロールORDSUsersを割り当てます。

ユーザーの割り当ては、エンタープライズアプリケーションに移動して実施します。

概要から、ローカルディレクトリのマネージドアプリケーションのリンクを開きます。


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


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


先ほど作成したアプリロールORDSUsersを割り当てるユーザーを選択し、選択をクリックします。


割り当てるロール「ORDS REST APIの呼び出し」(値はORDSUsers)は、すでに選択されています。

割り当てをクリックします。


以上でユーザーにアプリロールORDSUsersが割り当てられました。

規定のディレクトリに戻り、アプリの登録を開きます。すべてのアプリケーションから、先ほど作成したORDS MCP2を開きます。


APIのアクセス許可を開き、アクセス許可の追加をクリックします。


所属する組織で使用しているAPIを開き、ORDS MCP2を探して選択します。


アプリケーションの許可を選択します。

アクセス許可としてアプリロールORDSUsersが表示されます。このORDSUsersチェックして、アクセス許可の追加を実行します。


アクセス許可にORDSUsersが追加されました。

アクセス許可の追加を再度クリックします。


先ほどと同様に、所属する組織で使用しているAPIを開き、ORDS MCP2を探して選択します。

今度は認可されたアクセス許可を選択し、アクセス許可として表示されたmcp:connectチェックして、アクセス許可の追加を実行します。


以上でアクセス許可の設定ができました。

Entra IDのアクセストークンをv2に変更します。

マニュフェストを開きます。


マニュフェストaccessTokenAcceptedVersionの設定を追加します。

Microsoft Graphアプリマニュフェストであれば"api"の下、AAD Graph アプリマニュフェストであれば、トップレベルに属性"accessTokenAcceptedVersion"があります。

この値をに変更します。もし無い場合は属性を追加します。

"accessTokenAcceptedVersion": 2,

変更または追加後、保存します。


Entra ID v2アクセストークンでは、ユーザーの識別子となるclaimとしてupnを使用します。

トークン構成を開き、オプションの要求の追加をクリックします。


トークンの種類アクセスを選択し、要求に含まれるupnチェックします。これはOAuth2.0のアクセストークンにclaimとしてupnを追加する、という作業です。

以上で追加をクリックします。


Microsoft Graph profileのアクセス許可を有効にしますチェックして、追加します。


必ずしも必要ではないようですが、念の為同様の操作を行い、IDトークンについてもclaimとしてupn追加します。


以上でIDトークンとアクセストークンの両方に、属性としてupnが含まれるようになりました。

属性upnに設定される値を調整します。要求upnの3点メニューをクリックし、編集を実行します。


UPNの編集画面で、外部認証済みはいハッシュ記号の置換はいに設定します。

以上で保存します。


同じ作業をIDトークンとアクセストークンの両方で実施します。


以上で一旦、サーバーに対応したアプリケーションの設定は完了です。


次にクライアントに対応したアプリケーションを作成します。

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


作成するアプリケーションの名前ORDS MCP2 Clientとします。リダイレクトURIとして、ブラウザ版ChatGPTとClaude DesktopのURIの2つを設定する必要があるため、ここでの設定は省略します。

以上で登録します。


アプリケーションORDS MCP2 Clientが作成されます。

後ほどサーバー側のアプリケーションORDS MCP2への紐付けに使うため、アプリケーション(クライアント)IDコピーしておきます。その後に、リダイレクトURIを追加するのリンクを開きます。


リダイレクトURIの追加をクリックし、以下の2つのURIを追加します。

https://chatgpt.com/connector_platform_oauth_redirect
https://claude.ai/api/mcp/auth_callback


プラットフォームとしてWebを選択します。MCP InspectorはSPAなので、ブラウザ版ChatGPTやClaude DesktopもSPAとして扱うと勝手に考えていましたが、ChatGPTに相談したところ、リダイレクトURIがhttps://chatgpt.comやhttps://claude.aiならWebです、とChatGPTに指摘されました。

OktaやAuth0、Oracle IAMはSPAとして設定できるので、Entra IDではWebである理由は不明です。Entra IDでの認証時にはクライアントIDに加えてクライアント・シークレットの指定も必要なので、DCRとPKCEが、ChatGPTやClaude Desktopが期待しているようには動作しないのかもしれません。


リダイレクトURIにChatGPTのリダイレクトURIを設定し、構成を実行します。

https://chatgpt.com/connector_platform_oauth_redirect


同様にClaude DesktopのリダイレクトURI構成します。

https://claude.ai/api/mcp/auth_callback


設定を開き、パブリッククライアントフローを許可します。

パブリッククライアントフローを許可するとPKCEが実行されるため、クライアント・シークレットの指定は不要になるはずですが、そうなっていません。クライアント・シークレットが必要ということは、PKCEは実行されていないと思われるので、パブリッククライアントフローを許可する必要は無いかもしれません。


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


管理証明書とシークレットを開き、新しいクライアントシークレットを作成します。


説明secretと記述し、有効期限はデフォルトの推奨: 180 日(6ヶ月)を選択し、クライアントシークレットを追加します。


追加されたシークレットの値(シークレットIDではない)をコピーし、保存しておきます。ChatGPTのアプリやClaude Desktopのカスタムコネクタを作成する際に使用します。


アプリの登録に戻り、アプリケーションORDS MCP2を開きます。


APIの公開を開き、クライアントアプリケーションの追加を実行します。


クライアントIDにクライアントに対応するアプリケーションORDS MCP2 Clientアプリケーション(クライアント)IDを設定します。

承認済みのスコープをチェックし、アプリケーションの追加を実行します。


承認済みのクライアント・アプリケーションとしてORDS MCP2 Clientが追加されます。


以上でEntra IDの設定は完了です。


ORDSおよびnginxの設定



以下のスクリプトを実行し、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 opc]# cd /usr/share/nginx/html

[root@apex html]# 


ディレクトリ.well-knownを作成します。

mkdir .well-known

[root@apex html]# mkdir .well-known

[root@apex html]# 


以下を記述したファイルを、.well-known/oauth-protected-resourceとして作成します。
{
  "resource": "アプリケーションORDS MCP2のアプリケーション(クライアント)ID",
  "authorization_servers": [ 
    "https://login.microsoftonline.com/テナントID/v2.0"
  ],
  "scopes_supported": ["api://アプリケーションORDS MCP2のアプリケーション(クライアント)ID/mcp:connect"]
}
resourceの値は、サーバーに対応したアプリケーションORDS MCP2概要にあるアプリケーション(クライアント)IDです。


authorization_serversはJSON配列で要素となる値は、概要のエンドポイントにあるOpenID Connectメタデータドキュメントの末尾の/.well-known/openid-configurationを除いた部分です。


scopes_supportedはJSON配列で要素となる値は、APIの公開にあるスコープです。


authorization_serversが認識されない場合を想定し、認可サーバーのメタデータをリソース・サーバーに配置します。

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

curl -O https://login.microsoftonline.com/********-****-****-****-************/v2.0/.well-known/openid-configuration
mv openid-configuration .well-known/oauth-authorization-server

[root@apex html]# curl -O 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   5325      0 --:--:-- --:--:-- --:--:--  5339

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

[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;


/etc/nginx/default.d/01-apex.confを開き、WWW-Authenticateヘッダーがスコープを返すように変更します。scope=で指定する値は、/.well-known/oauth-protected-resourceのscopes_supportedに含まれている値です。

location @ords_401 {

    internal;

    more_clear_headers 'WWW-Authenticate';

    # The same IdP is used for all MCP servers.

    #add_header WWW-Authenticate 'Bearer resource_metadata="$scheme://$host/.well-known/oauth-protected-resource"' always;

    # scope for Microsoft Entra ID

    add_header WWW-Authenticate 'Bearer resource_metadata="$scheme://$host/.well-known/oauth-protected-resource",scope="api://e375897b-30ea-4f7a-9437-93c50a006872/mcp:connect"' always;

    # “A dedicated IdP is assigned to each MCP server.

    #add_header WWW-Authenticate 'Bearer resource_metadata="$scheme://$host/.well-known/oauth-protected-resource$uri"' always;


以上の変更を実施し、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



ブラウザ版ChatGPTより接続してみます。

設定のアプリを開き、高度な設定のアプリを作成するを実行します。(ChatGPTが開発者モードである必要があります。)


新しいアプリの名前My Oracle Appとします。MCPサーバーのURLとして、ORDS REST APIのエンドポイントURLを設定します。

認証OAuthを選択し、クライアントに対応したアプリケーションORDS MCP2 Clientアプリケーション(クライアント)IDクライアント・シークレットを指定します。

理解した上で、続行しますチェックし、作成するをクリックします。


アプリの作成は失敗しますが、extended.logにアクセストークンが出力されます。


アクセストークンをコピーし、https://jwt.ioなどを使って内容をデコードします。


アクセストークンに含まれる属性audおよび属性issの値をコピーします。また、rolesクレームが存在し、JSON配列の要素としてアプリロールORDSUsersが含まれていることを確認します。

属性audはORDS_SECURITY.CREATE_JWT_PROFILEの引数p_audienceの値になります。属性issは引数p_issuerの値になります。

ブラウザでOpenID ConnectメタデータのURLを開き、属性jwks_uriの値をコピーします。この値は、ORDS_SECURITY.CREATE_JWT_PROFILEの引数p_jwk_urlの値になります。


これらの値を引数に与えて、ORDS_SECURITY.CREATE_JWT_PROFILEを呼び出し、JWTプロファイルを作成します。引数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


以上で、ORDS RESTモジュールの保護も完了です。

再度、ChatGPTでアプリを作成します。ブラウザを再起動して実施した方がよいです。

以下のように接続に成功し、リモートMCPサーバーのツールがアクションとして表示されます。ここで使用したMCPサーバーはChatGPTアプリとしての実装は行なっていないため、何かができるわけではありませんが、OAuthで認証できることは確認できました。
 

Claude Desktopでも、ChatGPTと同じ設定でカスタムコネクタを追加します。


追加したカスタムコネクタMy Oracle App連携させます。


Entra IDでの認証プロセスが完了すると、連携/連携させるというボタンが設定に変わります。


設定をクリックすると、コネクタとして利用できるツールが確認できます。


Claude DesktopのカスタムコネクタはChatGPTとは異なり、リモートMCPサーバーとして利用できるようです。

そのため、チャットから呼び出すことができました。


今回の記事は以上です。