2025年12月12日金曜日

Oracle REST Data ServicesのRole based JWT profileによる保護を確認する

Oracle REST Data ServicesのRESTモジュールを保護するJWTプロファイルには、以下の2つの構成があります。
  • スコープ・ベースJWTプロファイル
  • ロール・ベースJWTプロファイル
公式ドキュメントの以下で説明されています。

Oracle REST Data Services, Release 25.3
Developer's Guide

注意1:2 Developing Oracle REST Data Services Applicationsの先頭にTopicsのリストがありますが、2.9 JWT Profile and JWT Profile RBACはTopicsに含まれていません。Configuring Secure Access to RESTful ServicesとJWT Bearer Token Authentication and Authorization Using JWT Profileの間にこのトピックがあります。

注意2:最新のORDSではパッケージOAUTHの代わりにORDS_SECURITYの使用が推奨されています。そのためJWTプロファイルの作成にはORDS_SECURITY.CREATE_JWT_PROFILEを呼び出すべきですが、このAPIのリファレンスにはRBACの実装に必要な引数p_role_claim_nameが含まれていません。OAUTH.CREATE_JWT_PROFILEのリファレンスには引数p_role_claim_nameが含まれています。これはドキュメントの問題で、ORDS_SECURITY.CREATE_JWT_PROFILEは、引数p_role_claim_nameをサポートしています。

公式ドキュメント以外に参考になるドキュメントとして、ORDSのプロダクト・マネージャであるChris Hoinaさんによるブログ記事があります。
Chris Hoinaさんの記事では、テストで使用するアプリケーションをNode.js / Express.js で作成していて、APEXアプリケーション開発者にはハードルが高いです。

本記事では、APEXアプリケーションを使って、ロール・ベースJWTプロファイルによる保護を実装します。以下の記事では、すべてスコープ・ベースJWTプロファイルによって、ORDSのRESTモジュールを保護しています。
それぞれのアイデンティティ・プロバイダにて、スコープ・ベースからロール・ベースJWTプロファイルによる保護に切り替えてみます。

最初に、ORDSのスコープ・ベースJWTプロファイルとロール・ベースJWTプロファイルによる保護の違いを説明します。

スコープ・ベースJWTプロファイルは、ORDS_SECURITY.CREATE_JWT_PROFILEを、引数p_role_claim_name無しで呼び出して作成します。このとき、RESTモジュールを保護するORDSの権限は、ORDS.DEFINE_PRIVILEGEを以下の引数で呼び出して作成します。権限名はmyordsapp、RESTモジュール名はprintとします。

スコープ・ベースJWTプロファイルによる保護では、権限名がアクセス・トークンのscopeまたはscpクレームに含まれていれば、権限に紐づいているRESTモジュールへのアクセスが認可されます。ロールを指定する必要はなく、指定があったとしても認可の可否に影響を与えません。
declare
    l_roles    owa.vc_arr;
    l_modules  owa.vc_arr;
    l_patterns owa.vc_arr;
begin
    l_modules(1) := 'print';
    ords.define_privilege(
        p_privilege_name => 'myordsapp',
        p_roles          => l_roles,      -- no assignment
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/
ロール・ベースJWTプロファイルによる保護では、アクセス・トークンに含まれるロールに、ORDSの権限に紐づけられたロールが含まれていれば、権限に紐づいているRESTモジュールへのアクセスが認可されます。権限名がスコープに含まれている必要はありません。アクセス・トークンのロールを含むクレームは、ORDS_SECURITY.CREATE_JWT_PROFILEの引数p_role_claim_nameに、JSONパスで指定します。
begin
    ords_security.delete_jwt_profile;
    ords_security.create_jwt_profile(
        p_issuer => 'https://sts.windows.net/********-****-****-****-************/'
        ,p_audience => 'api://********-****-****-****-************'
        ,p_jwk_url => 'https://login.microsoftonline.com/********-****-****-****-************/discovery/v2.0/keys'
        ,p_role_claim_name => '/roles'
    );
end;
/
上記ではp_role_claim_nameに/rolesを指定して、JWTプロファイルを作成しています。アクセス・トークンのrolesクレームに、ORDS側でロールとして作成したords_user_roleが含まれていれば、RESTモジュールprintへのアクセスが認可されます。スコープに権限名であるords_user_privilegeが含まれている必要はありません。
declare
    l_roles    owa.vc_arr;
    l_modules  owa.vc_arr;
    l_patterns owa.vc_arr;
begin
    ords.create_role(
        p_role_name => 'ords_user_role'
    );
    l_modules(1) := 'print';
    l_roles(1)   := 'ords_user_role';
    ords.define_privilege(
        p_privilege_name => 'ords_user_privilege',
        p_roles          => l_roles,
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/
ORDSのスコープ・ベースJWTプロファイルとロール・ベースJWTプロファイルによる保護には、以上のような違いがあります。

これらを踏まえて、それぞれのアイデンティティ・プロバイダーごとにロール・ベースJWTプロファイルの保護を実装してみます。すべてのケースで、スコープ・ベースJWTプロファイルによる保護が実装できている状態から作業を始めます。


Microsoft Entra ID



Entra IDのコンソールよりグループを開きます。Entra IDで作成するグループを、ORDSのロールに紐付けます。


新しいグループを作成します。


グループの種類にセキュリティを選びます。グループ名はORDSUsers、グループの説明は「ORDS REST APIへのアクセスを許可」と記述します。

以上でグループを作成します。


グループORDSUsersが作成されます。

すべてのグループからORDSUsersを開き、メンバーを追加します。


メンバーを開き、メンバーの追加を実行します。


グループに追加するメンバーを選択します。


ページを再ロードし、追加されたメンバーを確認します。


グループORDSUsersの概要を開きオブジェクトIDをコピーします。

ロール名にグループ名であるORDSUsersを使用したいのですが、Entra IDでグループ名をロールに含める方法を見つけられませんでした。そのため、ロール名にオブジェクトID(グループID)を使用します。


アプリケーションORDS JWT Testのトークン構成を開きます。

グループ要求の追加を実行します(翻訳はグループ要求ですが、ここでの要求はclaimのことで、groups claimの追加という意味です。)


ドロワーとして、グループの要求の編集が開きます。

先ほど作成したグループORDSUsersのタイプはセキュリティなので、アクセス、ID、SAMLトークンに含めるグループの種類を選択してください、のセキュリティをチェックします。

種類別にトークンのプロパティをカスタマイズする、ではID、アクセスともにグループIDを選択し、グループをロール要求として生成するをチェックします。

グループID以外を選択して動作を確認しましたが、何を設定してもアクセス・トークンにはグループ名ではなくグループIDがrolesクレームに含まれていたため、グループIDを選択しています。

グループをロール要求として生成するをチェックすると、アクセス・トークンにはrolesクレームにグループが含まれるようになります。未チェックの場合は、groupsクレームにグループが含まれます。ORDS_SECURITY.CREATE_JWT_PROFILEのp_role_claim_nameに/rolesと指定するか/groupsと指定するかの違いになりますが、今回はチェックを入れて、rolesクレームとなるようにしています。

詳しく調べていませんが、IDトークンとアクセス・トークンで異なる設定をすると、どちらかの設定が優先されるような動作になりました。IDトークンとアクセス・トークンで設定を変える必然性はないと思うので、同じ設定にするのが望ましいでしょう。

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


オプションの要求としてgroupsが追加されます。


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

ORDSのRESTモジュールを、ロール・ベースJWTプロファイルによる保護に変更します。

スコープ・ベースで保護されている状態で、APEXアプリケーションにサインインします。アクセス・トークンにrolesクレームとして、グループIDの配列が含まれていることが確認できます。
  "roles": [
    "aeb7eee2-b1ee-4127-91b5-58aaa9df63bd",
    "aa5a12e0-9c13-4a01-9a3f-95f978f2ce06"
  ],

ロール・ベースのJWTプロファイルを設定します。引数p_role_claim_nameに/rolesの指定を追加して、ORDS_SECURITY.CREATE_JWT_PROFILEを呼び出します。
begin
    ords_security.delete_jwt_profile;
    ords_security.create_jwt_profile(
        p_issuer => 'https://sts.windows.net/********-****-****-****-************/'
        ,p_audience => 'api://********-****-****-****-************'
        ,p_jwk_url => 'https://login.microsoftonline.com/********-****-****-****-************/discovery/v2.0/keys'
        ,p_role_claim_name => '/roles'
    );
end;
/

スコープ・ベースからロール・ベースの保護に切り替わりました。そのため、APEXアプリケーションをリロードすると、REST API Responseの表示はUnauthorizedに変わります。


ロール名がグループORDSUsersのグループIDであるロールを作成し、そのロールとRESTモジュールprintを紐づけた権限として、ORDSUsesを作成します。権限名のORDSUsersをアクセス・トークンのスコープに含む必要はありません。
declare
    l_roles    owa.vc_arr;
    l_modules  owa.vc_arr;
    l_patterns owa.vc_arr;
begin
    ords.create_role(
        p_role_name => 'aa5a12e0-9c13-4a01-9a3f-95f978f2ce06'
    );
    l_modules(1) := 'print';
    l_roles(1)   := 'aa5a12e0-9c13-4a01-9a3f-95f978f2ce06';
    ords.define_privilege(
        p_privilege_name => 'ORDSUsers',
        p_roles          => l_roles,
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/

上記の権限ORDSUsersが作成されるとREST APIの呼び出しは認可され、REST API Responseに呼び出し結果が表示されます。


以上でロール・ベースJWTプロファイルによる保護に切り替わったので、スコープ・ベースの設定は削除できるはずです。実際、ORDSの権限myordsappは削除できます。

ただし、認証スキームの有効範囲に設定したアプリケーションのURIを含むカスタム・スコープapi://********-****-****-****-****-***********/myordsappを削除すると、アクセス・トークンのiss属性やaud属性が変わる上に、rolesクレームがアクセス・トークンに含まれなくなります。結果として、RESTモジュールprintへのアクセスが認可されません。


認証スキームの有効範囲からカスタム・スコープを削除したいのですが、Microsoft Entra IDでどのようにすれば良いのかは、今のところ見つけられていません。


Okta



OktaのAdmin Consoleよりグループを開き、グループを追加します。


グループの名前はORDSUsers、説明は「ORDS REST APIへのアクセスを許可」と記述します。

保存をクリックします。


グループに再度アクセスし、グループ一覧を更新します。

作成したグループORDSUsersを開き、ユーザーを割り当てます。


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


グループORDSUsersにユーザーを割り当てます。


ユーザーの割り当てを完了します。


セキュリティのAPIより、作成済みの認可サーバーORDSを開きます。


クレームタブを開き、クレームを追加します。


追加するクレームの名前はrolesとします。トークンタイプに含めるはアクセス・トークンです。

値タイプにグループを選択し、フィルターは次で始まるでORDSを設定します。このフィルターにより、rolesクレームに先ほど作成したグループORDSUsersが含まれます。

クレームを無効化はチェックせず、含めるはいずれかのスコープを選択します。

以上で作成します。


以上でOktaの設定は完了です。

ロール・ベースJWTプロファイルを設定します。
begin
    ords_security.delete_jwt_profile;
    ords_security.create_jwt_profile(
        p_issuer => 'https://integrator-********.okta.com/oauth2/******************'
        ,p_audience => 'api://ords'
        ,p_jwk_url => 'https://integrator-********.okta.com/oauth2/******************/v1/keys'
        ,p_role_claim_name => '/roles'
    );
end;
/

スコープ・ベースからロール・ベースの保護に切り替わりました。そのため、APEXアプリケーションをリロードすると、REST API Responseの表示はUnauthorizedに変わります。

User Infoに表示されているアクセス・トークンの内容に、rolesクレームとして、ORDSUsersが含まれていることが確認できます。


ロールとしてORDSUsersを作成し、そのロールとRESTモジュールprintを紐づけた権限として、ORDSUsesを作成します。権限名のORDSUsersをアクセス・トークンのスコープに含む必要はありません。
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) := 'print';
    l_roles(1)   := 'ORDSUsers';
    ords.define_privilege(
        p_privilege_name => 'ORDSUsers',
        p_roles          => l_roles,
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/

上記の権限ORDSUsersが作成されるとREST APIの呼び出しは認可され、REST API Responseに呼び出し結果が表示されます。


Oktaの場合は、認証スキームの有効範囲からスコープmyordsappを削除できます。


また、ORDSの権限myordsappも不要なので削除できます。


Oktaの認可サーバーORDSに設定したスコープmyordsappも同様に削除できます。



Auth0


Auth0は趣が異なっていて、スコープ・ベースでもロール・ベースでも、Auth0側の設定は同じです。

Auth0で作成するのはパーミッションであり、パーミッションとして設定した名前が、スコープとpermissionsクレームに含まれます。Auth0のアクセス・トークンは以下のようになっています。
{
  "iss": "https://dev-************.us.auth0.com/",
  "sub": "google-oauth2|******************",
  "aud": [
    "api://ords/",
    "https://dev-************.us.auth0.com/userinfo"
  ],
  "iat": 1765528368,
  "exp": 1765614768,
  "scope": "openid profile myordsapp",
  "azp": "J3FejeK46E2EH8AhsDq53HylfgB30wyJ",
  "permissions": [
    "myordsapp"
  ]
}
そのため、ORDS側ではAuth0に作成したパーミッションmyordsappをscopeとして評価することもできるし、permissionsクレームに含まれた名前として評価することもできます。

以上よりAuth0の設定は変更せず、引数p_role_claim_nameに/permissionsを指定して、ロール・ベースJWTプロファイルを作成します。
begin
    ords_security.delete_jwt_profile;
    ords_security.create_jwt_profile(
        p_issuer => 'https://dev-************.us.auth0.com/'
        ,p_audience => 'api://ords/'
        ,p_jwk_url => 'https://dev-************.us.auth0.com/.well-known/jwks.json'
        ,p_role_claim_name => '/permissions'
    );
end;
/

permissionsクレームに含まれる名前myordsappを、ORDS側でロールとして作成します。
declare
    l_roles    owa.vc_arr;
    l_modules  owa.vc_arr;
    l_patterns owa.vc_arr;
begin
    ords.create_role(
        p_role_name => 'myordsapp'
    );
    l_modules(1) := 'print';
    l_roles(1)   := 'myordsapp';
    ords.define_privilege(
        p_privilege_name => 'ORDSUsers',
        p_roles          => l_roles,
        p_modules        => l_modules,
        p_patterns       => l_patterns    -- no assignment
    );
end;
/

 以上でORDSのRESTモジュールの保護がスコープ・ベースからロール・ベースに変更されました。

スコープは使用しないので、APEXアプリケーションの認証スキームの有効範囲からmyordsappを削除できます。


ORDSに権限として作成されているmyordsappも削除できます。


今回作成した権限ORDSUsersによって、RESTモジュールprintが保護されます。ロールとしてmyordsappを作成しています。


以上で、権限ORDSUsersによりREST APIの呼び出しは認可され、REST API Responseに呼び出し結果が表示されます。


Auth0にはロールの設定がありますが、これはパーミッションをまとめてユーザーに割り当てられるようにするための設定で、アクセス・トークンのpermissionsクレームには直接かロール経由かに関わらず、パーミッションの名前だけが含まれるようです。そのため、ORDSの保護という面では、意識する必要はなさそうです。

Oracle IAMについては、以下の記事で確認しています。

Oracle IAMの統合アプリにカスタム・クレームを追加しRole based JWT profileによる保護を確認する
https://apexugj.blogspot.com/2025/12/protecting-ords-by-role-based-jwt-profile-with-oracle-iam.html

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

Oracle APEXのアプリケーション作成の参考になれば幸いです。

完