2025年10月27日月曜日

Anton Schefferさんによるパスキーを使ったAPEXアプリへのサインインの実装を確認する

現在はOracle Corporationに所属されているAnton Schefferさんによる、APEXアプリケーションにパスキー認証を組み込むプラグインを使用してみます。Anton Schefferさんはas_cryptoという、RSAやAESなどの暗号ライブラリをPL/SQLで実装したすごい人です。本記事のパスキーの実装でもDBMS_CRYPTOは使用せず、AS_CRYPTOのRSAの実装を流用しています。

APEXでパスキーを使用する実装は以前から公開されていましたが、Anton Schefferさんが使いやすいようにサンプル・アプリにしてくれました。


本記事ではこのサンプル・アプリをインストールして、APEXアプリをパスキーで認証する方法を確認します。

APEX PasskeysのGitHubリポジトリより、サンプル・アプリのエクスポートをダウンロードします。f101.sqlとしてリポジトリに保存されています。サンプル・アプリはAPEX 22.2で作成されているため、アプリをインストールできるAPEXのバージョンは22.2以降になります。



f101.sqlをダウンロードした後に、これをAPEXのワークスペースにインポートします。パスキーで認証するにはHTTPSとDNSに登録された正式なホスト名が必要なため、Always FreeのAutonomous Databaseまたはorclapex.comに作成したワークスペースにアプリケーションをインストールするとよいでしょう。

アプリケーション・ビルダーを開き、インポートをクリックします。


インポートするファイルとして、先ほどGitHubよりダウンロードしたf101.sqlを選択します。ファイルタイプはデフォルトのアプリケーション、ページまたはコンポーネントのエクスポートとします。

へ進みます。


自動的に選択されているオプションは変更は不要なので、そのままアプリケーションのインストールをクリックします。

インポートしたアプリケーションの名前はDemo for Passkeysです。


サンプル・アプリDemo for Passkeysがインストールされます。必要なデータベース・オプジェクトはプラグインが必要に応じて作成するため、そのままアプリケーションの実行ができます。

今回は最初にアプリケーションの編集を開き、テーマのリフレッシュを実施します。


ページを構成する要素としては単純なものだけが使用されているため、テーマをリフレッシュしても問題は発生しません。

テーマのリフレッシュをクリックします。


テーマのリフレッシュが完了したら、アプリケーションを実行してパスキーによる認証を確認します。


サンプル・アプリケーションの説明ページが表示されます。

Macintoshで動くかどうかわからない、と記載されていますが、macOSでも動作しました。そのほかに、パスキーを使用する手順が説明されています。
  1. 最初にスキーム・タイプ公開資格証明(ユーザー名だけで認証するテスト用の認証手段)またはOracle APEXアカウントでユーザー認証をします。
  2. 1でユーザー認証したデバイスでパスキーを登録します。
  3. この後から、同じデバイスであればパスキーでユーザー認証できます。
ナビゲーション・メニューから、Registerのページを開きます。まだ、APEXアプリケーションにはサインインしていないため、右上のユーザーはnobodyになっています。


サインイン画面が開きます。

パスキーが未登録の場合は、Opendoor Sign InもしくはAPEX Account Sign Inのどちらかを実施して、アプリケーションにサインインします。

ユーザー名の入力は不要です。


今回はユーザー名を自由に決められるOpendoor Sign Inを実施します。

ユーザー名を入力し、Sign Inをクリックします。本来は初回のサインインでも、ユーザー名やパスワードの入力を要求すべきです。


スキーム・タイプ公開資格証明なので、ユーザー名が何でも、ユーザー認証に成功します。

ページにあるボタンRegisterをクリックすると、パスキーが登録されます。


私のmacOSの環境では、MacbookのTouch IDでパスキーを保存するかどうか、確認されました。


Touch IDでの指紋認証が完了すると、registeredとポップアップが表示されます。

OKをクリックしてポップアップを閉じます。


現時点ではアプリケーションは公開資格証明で認証されているので、一旦、サインアウトします。


サインアウト後、再度ナビゲーション・メニューよりRegisterを開きます。

Sign Inをクリックし、パスキーによるサインインを実施します。


MacbookではTouch IDによる認証が要求されます。

生体認証については、デバイスごとに手順は異なるでしょう。


Touch IDによる指紋認証に成功すると、パスキーを登録したユーザーでサインインが完了します。


サンプル・アプリケーションのパスキーによる認証は、以上のように動作します。

macOSのパスワード・アプリを開くと、パスキーが登録されていることが確認できます。


以下より、パスキーによる認証の実装について紹介します。

データベース・オブジェクトとしては表AS_USER_PASSKEYSとパッケージAS_PASSKEYSが作成されます。表AS_USER_PASSKEYSには、APEXのワークスペースID、アプリケーションID、サインインするユーザー名とそのユーザに紐づいたパスキーが保存されます。

AS_USER_PASSKEYSのDDLは以下です。列PASSKEYSの型はCLOBですが、JSON形式のパスキー(WebAuthn認証情報)が保存されます。
create table as_user_passkeys
  ( id number generated always as identity constraint as_user_passkeys2_pk primary key
  , workspace_id number not null
  , app_id       number not null
  , name         varchar2(4000 char) not null
  , extra        varchar2(4000 char)
  , passkeys     clob
  )
パッケージAS_PASSKEYSの定義は以下です。
create or replace package as_passkeys
is
  function get_version
  return varchar2;

  function render
    ( p_dynamic_action apex_plugin.t_dynamic_action
    , p_plugin         apex_plugin.t_plugin
    )
  return apex_plugin.t_dynamic_action_render_result;

  function ajax
    ( p_dynamic_action apex_plugin.t_dynamic_action
    , p_plugin         apex_plugin.t_plugin
    )
  return apex_plugin.t_dynamic_action_ajax_result;

  function verify_authentication( p_username varchar2 )
  return boolean;

  function passkey_authentication
    ( p_username varchar2
    , p_password varchar2
    )
  return boolean;

end as_passkeys;
パスキーによる認証は、主に動的アクションのプラグインとして作成されています。

ファンクションrenderでは、このカスタム・プラグインを組み込んだページに挿入するHTMLやJavaScriptを生成します。

ファンクションajaxは、ボタンRegisterおよびSign Inをクリックしたときに呼び出される、データベース・サーバー側の処理になります。

ファンクションpasskey_authenticationは、カスタム認証スキームの認証ファンクションとして使用します。実際はユーザー名のみを引数として、ファンクションverify_authenticationを呼び出しています。

ほとんどの実装は動的アクション・プラグインのAS Passkeyに含まれています。


動的アクション・プラグインのAS Passkeyコールバックレンダリング・プロシージャ/ファンクション名としてinit_plugin_and_renderが設定されています。

このコードは、プラグインのソースPL/SQLコードに記述されています。


ファンクションinit_plugin_and_renderでは、if init_table and init_package( p_plugin )という条件で、表AS_USER_PASSKEYSとパッケージAS_PASSKEYSの存在を確認し、それらが存在すればas_passkeys.renderを呼び出して、動的アクション(JavaScript)から呼び出すコードを生成しています。
function init_plugin_and_render
  ( p_dynamic_action apex_plugin.t_dynamic_action
  , p_plugin         apex_plugin.t_plugin
  )
return apex_plugin.t_dynamic_action_render_result
is
  l_rv apex_plugin.t_dynamic_action_render_result;
begin
  if init_table and init_package( p_plugin )
  then
    execute immediate 'begin :x := as_passkeys.render( :p1, :p2 ); end;' using out l_rv, p_dynamic_action, p_plugin;
  end if;
  return l_rv;
end init_plugin_and_render;
ファンクションinit_tableでは、動的SQLとして以下を実行して、例外が発生したらDDLを実行しています。

declare x as_user_passkeys%rowtype; begin null; end;

ビューUSER_TABLESやALL_TABLESを検索して確認するには、アクセス権限が必要だったり、オブジェクト数が多い場合は検索に時間がかかるので、このような手法は合理的です。
function init_table
return boolean
is
  e_not_declared exception;
  pragma exception_init( e_not_declared, -6550 );
begin
  begin
    execute immediate 'declare x as_user_passkeys%rowtype; begin null; end;';
  exception
    when e_not_declared then
      apex_debug.warn( 'table as_user_passkeys does not exist' );
      execute immediate '
create table as_user_passkeys
  ( id number generated always as identity constraint as_user_passkeys2_pk primary key
  , workspace_id number not null
  , app_id       number not null
  , name         varchar2(4000 char) not null
  , extra        varchar2(4000 char)
  , passkeys     clob
  )
';
      apex_debug.trace( 'table as_user_passkeys created' );
      execute immediate '
alter table as_user_passkeys
  add constraint as_user_passkeys_uk unique( workspace_id, app_id, name )
';
      apex_debug.trace( 'unique key for as_user_passkeys created' );
  end;
  execute immediate 'declare x as_user_passkeys%rowtype; begin null; end;';
  apex_debug.info( 'table as_user_passkeys exists' );
  return true;
end init_table;
パッケージAS_PASSKEYSも同じように存在確認に例外を使っています。パッケージ定義部と本体を記述したファイルは、プラグインの添付ファイルになっています。


ファンクションinit_packageではパッケージをインストールするにあたって、プラグインの添付ファイルを実行しています。そのため、静的アプリケーション・ファイルやインストール・スクリプトを別途用意する必要が無く、プラグインだけで、プラグインの実行に必要なデータベース・オブジェクトが作成されます。

ボタンRegisterまたはSign Inをクリックしたときに呼び出されるファンクションとして、パッケージAS_PASSKEYSに含まれるファンクションAJAXが呼び出されるように、コールバックAJAXプロシージャ/ファンクション名が設定されています。


ボタンRegisterSign Inの動作は、カスタム属性Usageによって切り替えています。


Usageでは、RegisterAuthenticate(値はregisterauthenticate)を選択できます。


ボタンRegisterをクリックしたときの、動的アクションAS Passkey[プラグイン]設定UsageRegisterに設定されています。つまり、パスキーの登録作業が呼び出されます。


パスキーでサインインするボタンSign In(外観がホットのボタン)をクリックしたときの、動的アクションAS Passkey[プラグイン]設定UsageAuthenticateに設定されています。つまり、パスキーによる認証作業が呼び出されます。


動的アクション・プラグインが組み込まれたページには、AS_PASSKEYS.RENDERが生成したHTML/JavaScriptが挿入されます。

apex_javascript.add_libraryにより、プラグインに含まれているファイルwebauthn.js(またはwebauthn.min.js)がページに組み込まれます。また、ボタンクリック時にファイルwebauthn.jsに記述されているファンクション_webauthnが呼び出されるように記述されています。動的アクションのattribute_01としてプラグインの設定Usageに設定したregisterまたはauthenticateが渡され、それを引数としてサーバー側のファンクションAS_PASSKEYS.AJAXが呼び出されます。
  function render
    ( p_dynamic_action apex_plugin.t_dynamic_action
    , p_plugin         apex_plugin.t_plugin
    )
  return apex_plugin.t_dynamic_action_render_result
  is
    l_result apex_plugin.t_dynamic_action_render_result;
  begin

    if apex_application.g_debug
    then
      apex_plugin_util.debug_dynamic_action
        ( p_plugin         => p_plugin
        , p_dynamic_action => p_dynamic_action
        );
    end if;

    apex_debug.trace( '%s render: %s', p_plugin.name, p_dynamic_action.attribute_01 );

    apex_javascript.add_library( p_name      => 'webauthn#MIN#'
                               , p_directory => p_plugin.file_prefix
                               , p_version => null
                               );

    l_result.attribute_01 := p_dynamic_action.attribute_01;
    l_result.attribute_02 := p_dynamic_action.attribute_02;
    l_result.attribute_03 := p_dynamic_action.attribute_03;
    l_result.attribute_04 := p_dynamic_action.attribute_04;
    l_result.ajax_identifier := apex_plugin.get_ajax_identifier;
    l_result.javascript_function := '_webauthn';

    return l_result;

  end render;
パスキーの登録と認証のフローは以上です。

最後にAPEXアプリケーションをパスキーで認証した上でセッションを継続するために、認証スキームとしてPasskeysが作成されています。


スキーム・タイプはカスタム設定認証ファンクション名としてAS_PASSKEYS.PASSKEY_AUTHENTICATIONが設定されています。


ファンクションAS_PASSKEYS.PASSKEY_AUTHENTICATION(実際はVERIFY_AUTHENTICATION)では、設定UsageAuthenticateの動的アクションで認証されたときに、AS_PASSKEYS.AJAXがAPEXコレクションに挿入した認証子の有無を確認して、認証を引き継いでいます。

パスキー自体の生成や確認については、概ねパスキーの仕様に基づいたコードがパッケージAS_PASSKEYSに含まれています。

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

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

2025年10月17日金曜日

Protégé DesktopにOracle Pluginを組み込む

Stanford大学が開発しているオープン・ソースのオントロジー・エディタProtégéのデスクトップ版Protégé Desktopに、Oracleに接続するプラグインを組み込んでみます。

OracleのプラグインはOracle JDK 17以降で動作します。Protégé Desktopに含まれているOpenJDK 11のJREでは動作しません。そのため、Protégé Desktopのプラットフォーム非依存版を使用します。

Protégé DesktopにOracleのプラグインを組み込むには、以下のソフトウェアが必要です。
  • Oracle JDK 17以降
  • Protégé Desktopのプラットフォーム非依存版
  • Protégé DesktopのOracleプラグイン
それぞれのアーカイブをダウンロードして、Oracleプラグインを組み込んだ状態でProtégé Desktopを起動するまでの作業を実施します。

作業はmacOSで実施します。Protégé Desktopのプラットフォーム非依存版を使用するため、LinuxやWindowsでも、概ね似たような手順でセットアップができるのではと思います。


Oracle JDK 17のダウンロード



OracleのJava Downloadsのページにアクセスします。


本記事はmacOSで作業しているため、macOSのARM64 DMG Installerをダウンロードしてインストールします。


それぞれの環境に合わせたJDKをダウンロードし、インストールします。


Protégé Desktopのプラットフォーム非依存版のダウンロード



GitHub上のProtégéのReleasesのページを開きます。


最新リリースのAssetsに含まれる、-platform-independent.zipが付いたアーカイブをダウンロードします。2025年10月17日現在では、Protege-5.6.8-platform-independent.zip がそのファイルになります。



Oracleプラグインのダウンロード



Protégé DesktopのOracleプラグインを含むアーカイブは、Oracle Software Delivery Cloudからダウンロードします。


サインインにはOracleプロファイルが必要です。

Oracle Graph - Support Adaptors and Pluginsで検索します。

いくつかのパッケージが見つかります。その中の以下をダウンロードします。

Oracle Graph - Support Adaptors and Plugins 19c and 23a


リンクをクリックしてダウンロードするアイテムとして追加します。

View Itemsをクリックすると、ダウンロード対象のアイテムを確認できます。

Continueをクリックします。


Oracle Graph - Support Adaptors and Plugins 19c and 23a をチェックし、Continueをクリックします。


ライセンス確認の画面が表示されます。同意するとダウンロードのページが開きます。

V1051657-01(V1051657-01.zip)のリンクをクリックします。V1051657-01.zipが手元にダウンロードされます。


以上でProtégé Desktopのプラグインを含んだアーカイブがダウンロードできました。


Protégé Desktopの実行



Oracle JDK 17はインストール済みとします。作業ディレクトリを作成し、ダウンロードした2つのアーカイブ、Protege-5.6.8-platform-independent.zipV1051657-01.zipを配置します。

Protege % ls

Protege-5.6.8-platform-independent.zip V1051657-01.zip

Protege % 


ファイルV1051657-01.zipに含まれるprotege_pluginを解凍します。

unzip V1051657-01.zip "protege_plugin/*"

Protege % unzip V1051657-01.zip "protege_plugin/*"

Archive:  V1051657-01.zip

   creating: protege_plugin

  inflating: protege_plugin/OraclePlugin_Protege550-3.12.0.jar  

   creating: protege_plugin/doc

  inflating: protege_plugin/doc/Protege5_5_Guide.pdf  

  inflating: protege_plugin/OWLUpdater.jar  

Protege % 


Protégé Desktopを解凍します。

unzip Protege-5.6.8-platform-independent.zip

ディレクトリProtege-5.6.8が作成され、その下にファイルが作成されます。
 

Protege % unzip Protege-5.6.8-platform-independent.zip 

Archive:  Protege-5.6.8-platform-independent.zip

   creating: Protege-5.6.8

   creating: Protege-5.6.8/app

   creating: Protege-5.6.8/bundles

   creating: Protege-5.6.8/conf

   creating: Protege-5.6.8/conf/deprecation

   creating: Protege-5.6.8/plugins

  inflating: Protege-5.6.8/app/Protege.icns  

  inflating: Protege-5.6.8/app/Protege.ico  

  inflating: Protege-5.6.8/run.bat   

  inflating: Protege-5.6.8/bundles/guava.jar  

  inflating: Protege-5.6.8/bundles/protege-launcher.jar  

  inflating: Protege-5.6.8/bundles/pfl-dynamic.jar  

  inflating: Protege-5.6.8/bundles/org.eclipse.equinox.registry.jar  

  inflating: Protege-5.6.8/bundles/gmbal.jar  

  inflating: Protege-5.6.8/bundles/jul-to-slf4j.jar  

  inflating: Protege-5.6.8/bundles/owlapi-osgidistribution.jar  

  inflating: Protege-5.6.8/bundles/asm-util.jar  

  inflating: Protege-5.6.8/bundles/management-api.jar  

  inflating: Protege-5.6.8/bundles/jaxb-api.jar  

  inflating: Protege-5.6.8/bundles/protege-common.jar  

  inflating: Protege-5.6.8/bundles/protege-editor-owl.jar  

  inflating: Protege-5.6.8/bundles/org.eclipse.jgit.jar  

  inflating: Protege-5.6.8/bundles/glassfish-corba-orb.jar  

  inflating: Protege-5.6.8/bundles/maven-artifact.jar  

  inflating: Protege-5.6.8/bundles/logback-classic.jar  

  inflating: Protege-5.6.8/bundles/slf4j-api.jar  

  inflating: Protege-5.6.8/bundles/logback-core.jar  

  inflating: Protege-5.6.8/bundles/asm-analysis.jar  

  inflating: Protege-5.6.8/bundles/org.eclipse.equinox.supplement.jar  

  inflating: Protege-5.6.8/bundles/asm.jar  

  inflating: Protege-5.6.8/bundles/jsr305.jar  

  inflating: Protege-5.6.8/bundles/javax.activation.jar  

  inflating: Protege-5.6.8/bundles/org.apache.servicemix.bundles.javax-inject.jar  

  inflating: Protege-5.6.8/bundles/glassfish-corba-omgapi.jar  

  inflating: Protege-5.6.8/bundles/pfl-basic.jar  

  inflating: Protege-5.6.8/bundles/JavaEWAH.jar  

  inflating: Protege-5.6.8/bundles/org.eclipse.equinox.common.jar  

  inflating: Protege-5.6.8/bundles/asm-tree.jar  

  inflating: Protege-5.6.8/bundles/commons-io.jar  

  inflating: Protege-5.6.8/bundles/protege-editor-core.jar  

  inflating: Protege-5.6.8/bundles/jaxb-core.jar  

  inflating: Protege-5.6.8/bundles/log4j-over-slf4j.jar  

  inflating: Protege-5.6.8/bundles/glassfish-corba-internal-api.jar  

  inflating: Protege-5.6.8/bundles/pfl-tf.jar  

  inflating: Protege-5.6.8/bundles/jaxb-impl.jar  

  inflating: Protege-5.6.8/bundles/org.apache.felix.main.jar  

  inflating: Protege-5.6.8/conf/deprecation/obi.yaml  

  inflating: Protege-5.6.8/conf/deprecation/basic.yaml  

  inflating: Protege-5.6.8/conf/deprecation/go.yaml  

  inflating: Protege-5.6.8/conf/logback.xml  

  inflating: Protege-5.6.8/conf/logback-win.xml  

  inflating: Protege-5.6.8/conf/config.xml  

  inflating: Protege-5.6.8/conf/jvm.conf  

  inflating: Protege-5.6.8/run.sh    

  inflating: Protege-5.6.8/plugins/explanation-workbench-3.0.1.jar  

  inflating: Protege-5.6.8/plugins/sparql-query-plugin-3.0.0.jar  

  inflating: Protege-5.6.8/plugins/code-generation-2.0.0.jar  

  inflating: Protege-5.6.8/plugins/owldoc-3.0.3.jar  

  inflating: Protege-5.6.8/plugins/existentialquery-2.0.0.jar  

  inflating: Protege-5.6.8/plugins/rdf-library-3.0.0.jar  

  inflating: Protege-5.6.8/plugins/ontograf-2.0.3.jar  

  inflating: Protege-5.6.8/plugins/elk-protege-0.6.0.jar  

  inflating: Protege-5.6.8/plugins/cellfie-2.1.0.jar  

  inflating: Protege-5.6.8/plugins/owlviz-5.0.3.jar  

  inflating: Protege-5.6.8/plugins/org.coode.dlquery-4.0.1.jar  

  inflating: Protege-5.6.8/plugins/swrltab-plugin-2.0.10.jar  

  inflating: Protege-5.6.8/plugins/org.semanticweb.hermit-1.4.3.456.jar  

  inflating: Protege-5.6.8/run.command  

Protege % 


ホーム・ディレクトリ直下に、プラグインを配置するディレクトリ~/.Protege/pluginsを作成します。

mkdir -p ~/.Protege/plugins

Protege % mkdir -p ~/.Protege/plugins

Protege % 


ディレクトリprotege_pluginの下にあるOraclePlugin_Protege550-3.12.0.jar ~/.Protege/pluginsの下にコピーします。この作業により、Protégé Desktopの起動時にOracleプラグインが組み込まれます。

cp protege_plugin/OraclePlugin_Protege550-3.12.0.jar ~/.Protege/plugins

Protege % cp protege_plugin/OraclePlugin_Protege550-3.12.0.jar ~/.Protege/plugins 

Protege % 


Protégé Desktopを起動します。ディレクトリProtege-5.8.6に移動します。

cd Protege-5.8.6

Protege % cd Protege-5.6.8

Protege-5.6.8 % 


デフォルトで選択されるjavaのバージョンが、Oracle JDK 17であることを確認します。

Protege-5.6.8 % java -version

java version "17.0.16" 2025-07-15 LTS

Java(TM) SE Runtime Environment (build 17.0.16+12-LTS-247)

Java HotSpot(TM) 64-Bit Server VM (build 17.0.16+12-LTS-247, mixed mode, sharing)

Protege-5.6.8 % 


Protégé Desktopの起動スクリプトrun.sh(Windowsではrun.bat)を実行します。

./run.sh

Protege-5.6.8 % ./run.sh

CompileCommand: exclude javax/swing/text/GlyphView.getBreakSpot bool exclude = true

******************************************************************************** 

**                                  Protege                                   ** 

******************************************************************************** 

 

----------------- Initialising and Starting the OSGi Framework ----------------- 

FrameworkFactory Class: org.apache.felix.framework.FrameworkFactory 

 

The OSGi framework has been initialised 

------------------------------- Starting Bundles ------------------------------- 

Starting bundle org.protege.common 

Starting bundle org.eclipse.equinox.common 

Starting bundle org.eclipse.equinox.supplement 

Starting bundle org.protege.editor.core.application 

Starting bundle org.eclipse.equinox.registry 

Starting bundle org.glassfish.pfl.pfl-dynamic 

Starting bundle log4j.over.slf4j 



[以下省略]


Protégé Desktopが起動します。メニューにOracleが含まれています。


PreferencesのPluginsを開くと、Oracle Plugin 3.12.0が組み込まれていることが確認できます。


今回の作業は以上になります。