Oracle REST Data Services 23.3から、サードパーティのIdPが生成したOAuth2のアクセス・トークンによる保護が実装されています。
ドキュメントの以下に説明が記載されています。
2.8 JWT Bearer Token Authentication and Authorization Using JWT Profile保護を設定するために、
OAUTH.CREATE_JWT_PROFILEを呼び出します。
Oracle APEXのアプリケーションの認証スキームをOpen ID Connectで構成して、動作を確認してみました。
認証プロバイダとしては、Okta、Microsoft Entra IDそれとOracle IDCSを試してみました。Oracle IDCSとOktaは動作させることができましたが、
Microsoft Entra IDはできませんでした。(Microsoft Entra IDでの確認については、
こちらの記事を参照してください。)
以下より、動作確認の手順を紹介します。この保護はどのように使うものか、確認手順から理解していきます。
最初に保護の対象とするORDSのRESTサービスを作成します。
このRESTサービスは、このRESTサービスを呼び出すAPEXアプリケーションとは異なるインスタンスに作成します。APEXアプリケーションと同じインスタンス(厳密にいうとワークスペース・スキーマ)のRESTサービスであれば、
こちらの記事で紹介しているApex-Sessionヘッダーによる認証を使うことができます。
以下のRESTfulデータ・サービスを作成します。
モジュール名: print
モジュール・ベース・パス: /print/
URLテンプレート: env
メソッド: GET
ソース・タイプ: PL/SQL
ソースにとして以下を記述します。
最初にバインド変数current_userの値を表示しています。current_userの値はJWTのサブジェクト(属性sub)の値が設定されることになっています。また、この値はCGI変数のREMOTE_IDENTにも設定されます。
続けて、RESTサービスが受け取ったHTTPリクエストのヘッダーを出力します。
begin
htp.p('<p>' || coalesce(:current_user, 'no_current_user') || '</p>');
owa_util.print_cgi_env;
end;
完全なURLをブラウザで開いて、HTTPヘッダーの情報が表示されることを確認します。
認証が必要な保護がかけられていないため、バインド変数current_userはNULLになっています。同様にCGI変数REMOTE_IDENTも空白になっています。
権限を作成し、作成したRESTサービスprintを保護します。
権限の名前はopenidとします。権限の名前はOAuth2のアクセス・トークン(=JWT)のスコープ(属性名としてはscope)に含まれている必要があります。本来であれば独自のスコープとなる名前にすべきですが、そうするとIdPに、この権限をカスタム・スコープとして作成しなければなりません。今回はRESTサービスの保護が確認できればよいので、Open ID Connectのアクセス・トークンに必ず含まれているopenidを権限の名前にします。
タイトルは任意の文字列です。
ロールとしてRESTful Services、保護されたモジュールにprintを選択します。
以上の設定で権限を作成します。
再度、完全なURLにアクセスします。HTTP 401、Unauthorizedのエラーが返されます。
Oracle IDCSにアプリケーションを登録します。
OCIコンソールのアイデンティティとセキュリティよりフェデレーションを開きます。
OracleIdentityCloudServiceを開きます。
Oracle Identity Cloud Service Consoleを開きます。
このコンソールのURLに含まれるホスト部分は、Open ID Connectの検出URLやJSON Web Key (JWK) URL - JWTの署名の検証に使う公開鍵を取得するURL - のホスト部分になるため、メモしておきます。
IDCSの画面が開きます。
最初に
設定の
デフォルト設定を開き、
署名証明書へのアクセスをOracle Identity Cloud Serviceにログインせずにできるように、スイッチを
オンに切り替えます。IDCSのデフォルトでは、JWK URLへのアクセスは保護されています。
Microsoft Entra IDやOktaでは、JWK URLはデフォルトで保護なしでした。
アプリケーションの追加を行います。認証プロトコルにはOpen ID Connectを使います。
あらかじめAPEX側を呼び出すコールバックURLを確認しておきます。Open ID Connectの場合は末尾がapex_authentication.callbackになります。
https://ホスト名/パス/apex_authentication.callback
機密アプリケーションを選択します。
動作確認に必要な、最低限の設定を行います。
アプリケーションの名前はORDS JWT Testとします。
次へ進みます。
認可の
許可される権限付与タイプの
認可コードに
チェックを入れます。
リダイレクトURLを入力し、次へ進みます。
リソース・サーバーの設定はスキップします。
次へ進みます。
Web層ポリシーの構成もスキップします。
次へ進みます。
認可についても設定せず、
終了をクリックします。
アプリケーションが追加されます。クライアントIDとクライアント・シークレットが表示されるので、これをコピーします。APEX側でWeb資格証明を作成するときに使用します。
作成したアプリケーションがアクティブ化されていない場合は、右端のメニューよりアクティブ化をします。
アプリケーションがアクティブ化されれば、IdP側の準備は完了です。
APEX側の作業を行います。
IDCSが発行するトークンを保存するWeb資格証明を作成します。
名前はOracle IDCS JWT Test、静的IDはORACLE_IDCS_JWT_TESTとします。
認証タイプとしてOAuth2クライアント資格証明フローを選択し、クライアントIDまたはユーザー名およびクライアント・シークレットまたはパスワードとして、IDCSへのアプリケーション登録時に表示されたクライアントIDとクライアント・シークレットを設定します。
空のAPEXアプリケーションを作成します。名前はORDS JWT Testとします。
ホーム・ページに、IdPが返すレスポンスとREST APIのレスポンスを表示します。
ホーム・ページは以下のように表示されます。IdPが返すaccess_tokenと、REST APIのレスポンスに含まれるAuthorizationヘッダに含まれるBearerトークンは、同じ文字列になります。
作成したアプリケーションには記事「
Open ID ConnectまたはOAuth2による認証の応答を印刷する」で紹介している、デバッグのための仕組みを組み込みます。ファンクション
dump_signon_responseが作成済みとします。
ファンクションdump_signon_responseの出力を保存するアプリケーション・アイテムとしてG_USER_INFOを作成します。
アプリケーション定義の置換に置換文字列としてG_REST_URLを設定します。置換値は、別インスタンスに作成したRESTサービスprint/envを呼び出す完全なURLです。
Oracle IDCSをIdPとした認証スキームを作成します。
名前はOracle IDCSとします。スキーム・タイプはソーシャル・サインインです。
設定の資格証明ストアとしてOracle IDCS JWT Test、認証プロバイダはOpenID Connectプロバイダを選択します。
Oracle IDCSのテナント識別子を含む、以下の形式のURLを検出URLとして設定します。
https://[IDCSテナント識別子].identity.oraclecloud.com/.well-known/openid-configuration
有効範囲にprofile、ユーザー名に#sub#を設定します。有効範囲はスコープのことですが、openidはデフォルトで追加されるため、有効範囲に含める必要はありません。
IdPのレスポンスをアプリケーション・アイテムG_USER_INFOに保存するコードを、ソースのPL/SQLコードに記述し、ログイン・プロセスの認証後のプロシージャ名にpost_authを設定します。
procedure post_auth is
begin
:G_USER_INFO := dump_signon_response;
end post_auth;
以上で、Oracle IDCSをIdPとして使用する認証スキームの設定ができました。
ホーム・ページにIdPのレスポンスを表示するページ・アイテムを作成します。
名前はP1_USER_INFO、タイプとしてテキスト領域を選択します。外観の高さに20を設定し、テキスト領域の高さを大きくしておきます。
ソースのタイプにアイテムを選択し、アイテムとしてG_USER_INFOを指定します。データ型にCLOBを選択します。
REST APIのレスポンスを表示するリージョンを作成します。
識別のタイトルはREST API Response、タイプとして動的コンテンツを選択します。
ソースのCLOBを返すPL/SQLファンクション本体に、以下のコードを記述します。
declare
l_static_id varchar2(80);
l_response clob;
begin
/* get static id of web credentials of current authentication scheme */
select c.static_id into l_static_id
from APEX_APPLICATION_AUTH a join APEX_WORKSPACE_CREDENTIALS c on a.attribute_01 = c.credential_id
where a.application_id = :APP_ID and a.is_current_authentication = 'Y';
apex_web_service.clear_request_headers();
l_response := apex_web_service.make_rest_request(
p_url => :G_REST_URL
,p_http_method => 'GET'
,p_credential_static_id => l_static_id
);
return l_response;
end;
表示されるデータ量が多いため、テンプレート・オプションのBody Heightとして320pxにしています。
以上でアプリケーションは完成です。
アプリケーションを実行すると、Oracle Cloudへのサインインが要求されます。
ユーザー認証に成功すると、作成したアプリケーションのホーム・ページが開きます。
User Infoのページ・アイテムにaccess_tokenが含まれています。REST API ResponseにはUnauthorizedのエラーが表示されています。
https://jwt.ioの
デバッガを開き、
User Infoに含まれている
access_tokenをデコードします。
Encodedに
access_tokenの値をペーストします。
HEADERに含まれるkidは、認証がうまくいかないときに確認することがあります。
PAYLOADから
OAUTH.CREATE_JWT_PROFILEの呼び出しに使用する値を見つけます。
属性issは引数p_issuerの値になります。属性audは引数p_audicenceの値になります。
今回は権限の名前をopenidとしているため気にする必要はありませんが、属性scopeに権限の名前が含まれていることも確認します。
引数p_jwk_urlに与える値は、認証スキームOracle IDCSの検出URLにアクセスして、レスポンスに含まれる属性jwks_uriから取得できます。
通常は以下の形式になります。
https://[IDCSテナント識別子].identity.oraclecloud.com:443/admin/v1/SigningCert/jwk
RESTサービスを実装したインスタンスのSQLコマンドを開いて、OAUTH.CREATE_JWT_PROFILEを実行します。
begin
oauth.create_jwt_profile(
p_issuer => 'issの値'
,p_audience => 'audの値'
,p_jwk_url => 'jwks_uriの値'
);
commit;
end;
OAUTH.CREATE_JWT_PROFILEを実行した後に、APEXアプリケーションのホーム・ページを再読み込みすると、JWTによる認証に成功してREST API Responseが正常に表示されます。
認証がうまくいかない場合は、引数p_jwk_urlとして与えたJWK URLを直接開いてみます。IDCSの設定の署名証明書へのアクセスをオンにするのを忘れていると、認証エラーが発生して公開キーの取得に失敗します。
Oracle IDCSの場合はkidが固定値のSIGNING_KEYであるため気にする必要はありませんが、他のIdPではJWKのkidと、access_tokenのヘッダーに含まれるkidが一致していることも確認すべき点です。
以上が、ORDS 23.3で追加されたOAUTH.CREATE_JWT_PROFILEを使う設定の紹介になります。
Oracle IDCSでは属性
subの値がメール・アドレスであるため、
current_userもメール・アドレスになりますが、IdPによっては
subの値がGUIDのような数値の場合もあります。APEXの
APP_USERは大体メール・アドレスとするため、属性
emailを
APP_USERにするように設定しますが、OAUTH.CREATE_JWT_PROFILEによる保護では今のところ
current_userを属性
emailの値にすることはできなさそうです。
Oktaでの確認
サイド・メニューのApplicationsからApplicationを開き、アプリケーションを作成します。
Create App Integrationをクリックします。
Sign-in methodに
OIDC - OpenID Connectを選択します。
Application typeは
Web Applicationを選択します。
Nextに進みます。
App integration nameはORDS JWT Testとします。
Sign-in redirect URIsにはAPEXを呼び出すコールバックURLを設定します。Sign-out redirect URIsは X をクリックして削除し、無設定にします。
今回は単にテストなので、AssignmentsのControlled accessにAllow everyone in your organization to accessを選択します。
以上でSaveします。
アプリケーション
ORDS JWT Testが登録されます。
Client IDとSecretをコピーします。APEX側でWeb資格証明を作成するときに使います。
Oktaに場合、defaultのAuthorization Serverではアクセス・トークンのkidとJWK URLのkidが一致しませんでした。そのため、SecurityのAPIよりAuthorization Serverを追加しています。
追加した
Authorization Serverの
Nameは
ORDS、
Audienceは
ordsとしています。
Metadata URIはOpenID Connectの
検出URLになるため、コピーして保存しておきます。
Access Policiesのタブを開き、Add New Access Policyをクリックして、Assigned toをAll Clientsとするポリシーを作成します。以下ではName、DescriptionともにAll Clientとしたポリシーを作成しています。
ほとんどすべてを許可するルールを追加しています。
以上でOkta側の準備は完了です。
APEX側でWeb資格証明を作成します。名前はOkta JWT Testとします。
クライアントIDまたはユーザー名およびクライアント・シークレットまたはパスワードには、Oktaに作成したアプリケーションのClient IDとSecretを設定します。
認証スキームOracle IDCSをコピーして、OktaをIdPとする認証スキームOktaを作成します。
資格証明ストアはOkta JWT Test、検出URLはAuthorization ServerのMetadata URIに変更します。以下のような形式になります。
https://[Oktaの認証サーバー]/[認証サーバーのID]/.well-known/oauth-authorization-server
変更を適用した後、カレント・スキームに変更します。
以上でOktaをIdPとした認証スキームへの切り替えが完了しました。
ブラウザを再起動し、APEXアプリケーションを実行します。
Oktaへのサインインが求められます。
APEXアプリケーションの画面が開きます。サインインには成功しているため、access_tokenは表示されますが、REST APIはUnauthorizedになります。
今までの作業と同様に、access_tokenをhttps://jwt.ioのデバッガにかけてissとaudを取得します。
これらの値は、Authorization ServerのAudienceとIssuer URIの値でした。
Authorization ServerのMetadata URIをアクセスし、jwks_uriの値を取得します。以下のような形式になります。
https://[認証サーバーのID].okta.com/oauth2/[Authorization ServerのID]/v1/keys
これらの値を与えて、
OAUTH.CREATE_JWT_PROFILEを実行します。
OktaでもREST APIは認証されません。
Oktaのアクセス・トークンのスコープも属性scpで渡されている上、スコープが空白区切りではなく、JSON配列になっています。
Oktaの開発者フォーラムにscope claimを追加する方法が紹介されていました。
Scope/scp and space delimited string in access tokenAuthorization Serverの
Claimsタブを開き、
Add Claimを実行します。

追加するClaimのNameがscopeになります。Include into token typeはAccess Token、Value typeはExpressionを選択し、Valueとして以下を記述します。
String.replace(Arrays.toCsvString(access.scope),","," ")
Saveをクリックします。
上記の手順にてscopeを追加すると、REST APIの認証に成功しました。
今回の検証で使用したAPEXアプリケーションのエクスポートを以下に置きました。OpenID Connectの検出URLと呼び出すREST APIのURLは伏せ字にしているため、環境に合わせて設定し直す必要があります。
https://github.com/ujnak/apexapps/blob/master/exports/ords-jwt-test.zip
以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完