1. はじめに
Oracle が 2026 年 5 月に Oracle Backend with Firebase APIs を公開しました1。Firebase 風の SDK(iOS / Android / JavaScript / Flutter)でモバイル・Web アプリを作り、バックエンドを Oracle AI Database が担う、という Backend-as-a-Service 風のツールキットです。Oracle REST Data Services(ORDS)26.1.1 に同梱される無料機能で、ORDS を導入した環境で利用できます1。
アナウンス記事には次の一文があります。
A VPD policy on a collection table fires on every Fusabase read and write(コレクション表に掛けた VPD ポリシーは、すべての読み書きで動作する)1
つまり「アプリ開発者が書く Security Rules」と「DBA が掛ける VPD・監査」は同じデータの上の 2 層になる、という主張です。アプリ開発者に Firebase 並みの手軽さを渡しながら、DBA が掛ける監査や VPD は SDK 経由の読み書きにも動作するのか。確かめるため、まずアプリを 1本作りました。そのうえで、同じデータベースに DBA として接続して中を確認しています。
1.1. 結論(先出し)
- 公式ドキュメントの手順どおりに進めても Console にサインインできず、ブラウザからも呼び出せない。ドキュメントに書かれていない設定が 3つ要る(7 章に補足)
- SDK が書き込んだ document の実体は、JSON 型の列を持つ通常の表だった。
SELECTで読み出せる - 統合監査も VPD も、SDK 経由の読み書きに対して動作する。ただし監査に残るデータベースユーザーは常に 1つで、アプリのどのユーザーが操作したかは分からない
- Security Rules による拒否は、対象の行を読んだ後に別のクエリで判定される。読ませない仕組みではなく、返さない仕組みだった
1.2. 検証ゴール
| # | 確かめること | 確認できれば OK の条件 |
|---|---|---|
| 1 | Web SDK でアプリが動く | 認証・書き込み・読み出しができ、他ユーザーのデータには到達できない |
| 2 | コレクションの保存先を SQL で特定できる | 実体のオブジェクトを特定し、SDK が書いた document を SELECT で読み出せる |
| 3 | DBA 側の統制が SDK 経由の操作に動作する | 統合監査に記録が残り、VPD を掛けると SDK に返る行が変わる |
対象読者は、Oracle Database を運用していて、アプリ開発チームからこのツールキットを使いたいと相談を受ける立場の方です。
2. 検証環境
すべて無料のもので作りました。
| 項目 | 内容 |
|---|---|
| データベース | Oracle AI Database 26ai Free 23.26.2.0.0(gvenzl/oracle-free:23.26.2) |
| ORDS | 26.2.2.r2041619(ords-latest.zip を手動展開、eclipse-temurin:21-jre-jammy 上で実行) |
| JavaScript SDK |
fusabase 26.1 系(npm) |
| CLI |
fusabase-cli(GitHub のソースから導入) |
| ホスト | Windows 11 Pro + Docker Desktop |
アプリと DBA が同じ表に別々の経路からアクセスする構成です。
3. アプリを作る(開発者として)
題材は家族の買い物リストにしました。親がリストを作り、家族以外からは読めない、という最小構成です。
3.1. SDK の書き方
初期化して setDoc でデータを書きます。関数名と引数は Firestore とほぼ同じです。
import { initializeApp } from 'fusabase/app';
import { getAuth, signInWithEmailAndPassword } from 'fusabase/auth';
import { getOracledb, doc, setDoc, getDocs } from 'fusabase/oracledb';
const app = initializeApp({
ords_host: 'http://localhost:8080/ords/fusabase_user/',
schema: 'fusabase_user',
app_id: 'xxxxxxxxxxxxxxxx',
app_type: 'web',
project_id: 'xxxxxxxxxxxxxxxx',
objs_type: 'none',
storage_bucket: 'none',
auth_type: 'base',
auth_id: 'xxxxxxxxxxxxxxxx',
});
const auth = getAuth(app);
const db = getOracledb(app);
// サインインしてから、自分の買い物リストに 1件書く
const cred = await signInWithEmailAndPassword(auth, email, password);
await setDoc(doc(db, 'lists', cred.user.uid, 'items', 'item-milk'), {
title: 'Milk',
done: false,
createdAt: Date.now(),
});
initializeApp に渡す app_type は、SDK の README のサンプルには出てきませんが、指定しないと Invalid app type のエラーになりました。
サインインすると自分のリストが表示されます。ここまでで書いたコードは 100行ほどです。
3.2. Security Rules
アクセス制御は CEL(Common Expression Language)で書きます。今回は「自分の ID のリストだけ読み書きできる」だけの 3行です。
match /lists/{familyId}/items/{itemId} {
allow read, write: if request.auth.uid == familyId;
}
Console のエディタから編集して発行でき、画面の右側にはルールを試せるサンドボックスがあります。
3.3. 拒否されるとどうなるか
親で 3件書き込み、子で親のリストを読みにいく検証を Node.js から実行しました。親側は 3件とも書き込め、同じ 3件を読み出せます。
setDoc ok: item-milk Milk
setDoc ok: item-eggs Eggs
setDoc ok: item-bread Bread
own list size (approx): 3
子で同じリストを読むとこうなりました。
child signed in, uid= 58276915EF1B0DC2E063020013AC7F5F
getDocs result: size= 0 (no exception thrown)
getDoc result: exists()= false data= undefined
例外は発生せず、空の結果が返ります。件数がゼロなのか、権限が無いのかは呼び出し側からは区別できません。権限不足を示すエラーは返らないため、アプリでエラー表示を出し分けるなら、件数以外の判定材料を用意することになります。このときデータベース側で何が実行されているかは、4.4 章で確認します。
書き込んだデータは Console のデータブラウザからも確認できます。
4. データベース側から見る(DBA として)
ここからは SQL*Plus で接続します。アプリが書いたデータが、Oracle の中でどんな形で保存されているかを確認します。
4.1. コレクションの実体
プロジェクト用スキーマのオブジェクトを一覧すると、BAAS_ で始まる管理用の表に混じって、ランダムな名前の表が 2つありました。対応関係は BAAS_COLLECTION_METADATA に載っています。
/lists -> DNHARCVFXZDP (type=doc)
/lists/_docId/items -> SSMCSCDZERKV (type=doc)
コレクションのパスが、そのまま表の名前になるわけではありません。DDL を取ると、中身は通常の表でした。
CREATE TABLE "FUSABASE_USER"."SSMCSCDZERKV"
( "DOCUMENT" JSON,
"CREATED" TIMESTAMP (6),
"LAST_MODIFIED" TIMESTAMP (6),
"VERSION" NUMBER,
"PARENT_OID" VARCHAR2(4000),
"OID" VARCHAR2(50) DEFAULT SYS_GUID(),
CONSTRAINT "SSMCSCDZERKV$_PK" PRIMARY KEY ("OID")
) TABLESPACE "FUSABASE_TBS"
JSON ("DOCUMENT") STORE AS (TABLESPACE "FUSABASE_TBS" ...);
document 本体は DOCUMENT 列に JSON 型で入り、更新時刻とバージョン、親子関係を表す PARENT_OID、主キーの OID が付く構成です。JSON Relational Duality View ではなく、JSON 型の列を持つ表でした。開発者ガイドには保存形式のはっきりした記述が見当たらないため、ここは実機で確認した結果です。
今回確かめたのは、SDK から新しく作ったコレクション 2つ(いずれもドキュメント型)です。開発者ガイドによれば、既存のリレーショナル表をコレクションとして見せるモデルも用意されており、そちらは表ごとに Duality View が生成されると説明されています2。同じ「コレクション」でも、作り方によって実体は変わります。
4.2. SQL で読む
通常の表なので、SELECT で読み出せます。
SELECT oid, parent_oid, document, created FROM SSMCSCDZERKV ORDER BY created;
item-milk /58276915EF1A0DC2E063020013AC7F5F
{"title":"Milk","done":false,"createdAt":1785790839504} 03-AUG-26 09.00.00.551290 PM
item-eggs /58276915EF1A0DC2E063020013AC7F5F
{"title":"Eggs","done":false,"createdAt":1785790839564} 03-AUG-26 09.00.00.640038 PM
item-bread /58276915EF1A0DC2E063020013AC7F5F
{"title":"Bread","done":false,"createdAt":1785790839614} 03-AUG-26 09.00.00.719669 PM
アプリが setDoc で書いた 3件が、DOCUMENT 列の JSON として返りました。PARENT_OID にはサインインしたユーザーの ID が入っています。既存の分析基盤や BI ツールから、このデータを SQL で扱えるということでもあります。
4.3. 統合監査に記録されるか
この表に統合監査ポリシーを掛けて、アプリから読み書きしてみます。
CREATE AUDIT POLICY fusab_items_pol
ACTIONS SELECT, INSERT, UPDATE, DELETE ON FUSABASE_USER.SSMCSCDZERKV;
AUDIT POLICY fusab_items_pol;
SDK 経由の操作は UNIFIED_AUDIT_TRAIL に記録されました。SQL 文まで残ります。
dbuser=FUSABASE_USER | client_id=[] | os_user=root | userhost=<ORDS のホスト>
program=Oracle REST Data Services | action=SELECT
SQL: select json_object(* returning json) "osons" from "SSMCSCDZERKV" T
where T.PARENT_OID='/58276915EF1A0DC2E063020013AC7F5F' order by T.oid
dbuser=FUSABASE_USER | client_id=[] | program=Oracle REST Data Services | action=UPDATE
SQL: update "SSMCSCDZERKV" set version=version+1, document = :document,
last_modified = systimestamp at time zone 'UTC'
where oid = :oid and parent_oid = :parent_oid returning OID,version into :roid,:rversion
確認したいのは dbuser と client_id の 2列です。親で操作しても子で操作しても、記録されるデータベースユーザーは常に FUSABASE_USER の 1つで、CLIENT_IDENTIFIER は空でした。操作したユーザーを示す値は SQL 文の中にしかありません。今回は lists/{ユーザー ID}/items というパスでデータを持たせたので、結果的に PARENT_OID の値がユーザー ID になっています。これは自分で決めたデータモデルによるものであって、ツールキットがエンドユーザーの識別子を監査列に設定するわけではありません。
4.4. 拒否されたリクエストは監査からどう見えるか
3.3 章で、子ユーザーが親のリストを読もうとすると空の結果が返ることを確認しました。このとき監査証跡に何が残るのかを見てみます。監査を有効にしたまま、子の拒否だけを実行して、その時間帯の証跡をすべて出力しました。
子のリクエストはデータベースに届いていました。実行された SELECT は、親が自分のリストを読んだときと同じ文・同じバインド値です。
13:26:46.846 select json_object(* returning json) "osons" from "SSMCSCDZERKV" T
where T.PARENT_OID='/58276915EF1A0DC2E063020013AC7F5F' order by T.oid
13:26:46.854 select 1 from sys.dual where :1 = :2
binds: #1 58276915EF1B0DC2E063020013AC7F5F #2 58276915EF1A0DC2E063020013AC7F5F
2 文目が認可の判定です。sys.dual に対して 2つの値が等しいかだけを判定する SELECT で、バインド値はサインインしたユーザーの ID と、パスに含まれる familyId でした。子の場合はこの 2つが食い違い(...EF1B0... と ...EF1A0...)、結果として SDK には空が返ります。親が同じ操作をしたときは、両方に同じ ID が入って一致していました。
CEL のルール request.auth.uid == familyId が、そのまま SQL の等値比較に変換されています。開発者ガイドの「CEL を SQL に変換して評価する」という説明2は、文字どおりの意味でした。
DBA 側から見て重要なのは実行順序です。データを読む SELECT が先に実行され、その 8 ミリ秒後に認可チェックの SELECT が実行されています。つまり拒否されるリクエストでも、対象の行はいったんデータベースから読み出されています。監査証跡の上では、拒否されたアクセスと成功したアクセスはデータ取得の行だけを見ると区別できません。判別するには、直後の sys.dual に対する比較とそのバインド値まで併せて見る必要があります。
4.5. VPD で行を隠せるか
行レベルのアクセス制御も試します。タイトルが Bread の行だけを除外するポリシーを掛けました。
-- 関数の中身は「title が Bread の行を除外する」条件を返すだけ
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'FUSABASE_USER',
object_name => 'SSMCSCDZERKV',
policy_name => 'FUSAB_ITEMS_VPD',
function_schema => 'FUSABASE_USER',
policy_function => 'FUSAB_HIDE_BREAD',
statement_types => 'SELECT,UPDATE,DELETE');
END;
/
アプリから同じリストを読むと、Bread が消えて 2件になりました。アナウンス記事の主張どおり、SDK 経由の読み取りにも VPD が適用されています。
一方、書き込み側では失敗が起きます。隠した item-bread に対して setDoc を実行すると、次のエラーになりました。
ORA-20018: ORA-00001: unique constraint (FUSABASE_USER.SSMCSCDZERKV$_PK) violated
監査証跡を見ると、setDoc は更新のたびに select document from ... where oid = :1 and PARENT_OID = :2 で 1行に絞る SELECT を実行してから UPDATE しています。VPD が既存行を隠したことでこの SELECT が 0行を返し、INSERT が実行されて主キー違反になったと考えられます。VPD の絞り込み条件に当たらない item-milk と item-eggs の setDoc は成功しました。
読み取りでは隠した行が SDK にも返りませんが、書き込み経路まで含めると、行を隠すことがアプリ側のエラーになります。
5. 考察
5.1. 監査に残るのは接続ユーザーだけで、アプリのどのユーザーかは分からない
コレクションの実体は通常の表であり、統合監査も VPD も SDK 経由の読み書きに対して動作しました。アプリ開発者が書く Security Rules とは独立に、DBA 側の統制が同じデータに掛かるという点は確認できました。
ただし監査証跡に残るデータベースユーザーは常に FUSABASE_USER の 1つで、CLIENT_IDENTIFIER は空でした。「このアプリが読んだ」までは分かりますが、「アプリのどのユーザーが読んだ」は監査列だけでは追えません。追うには PARENT_OID に埋まったユーザー ID か、SQL 文中のリテラルを解析することになります。
接続プールを共有する Web アプリでは一般的な構成であり、このツールキット固有の欠点ではありません。ただ、Firebase 風の SDK でエンドユーザーを認証している以上、その識別子がデータベース側にも渡ることを想定しがちです。今回の構成では渡りませんでした。エンドユーザー単位の追跡を要件に持つ場合は、アプリ側のログと組み合わせる設計が要ります。
5.2. Security Rules はデータベースで評価されるが、データ取得を絞ってはいない
開発者ガイドは、Security Rules を CEL で書き、Java のパーサーが SQL に変換して評価すると説明しています2。
監査証跡で確かめたところ、変換された SQL は select 1 from sys.dual where :1 = :2 という独立した 1 文でした。データを取りに行く SELECT の絞り込み条件に混ざるのではなく、別のクエリとして判定されます。判定に使うルール自体も BAASSYS.USER_BAAS_SECURITYRULES からリクエストのたびに読み出されていました。
順序は、データを読む SELECT が先で、認可の判定が後でした。拒否されたリクエストでも対象行はデータベースから読み出されており、その SELECT は成功したリクエストのものと同一です。読み出した行を返さない形になります。
運用上、2つの点に影響します。監査の読み方としては、コレクション表への SELECT だけを見ても、それが許可されたアクセスか拒否されたアクセスかは分かりません。設計としては、Security Rules は「アプリに返さない」仕組みではあっても「データベースから読ませない」仕組みではない、という点です。読み出し自体を止めたいなら VPD のようなデータベース側の機能と併用することになります。
今回試したのは request.auth.uid とパス変数を突き合わせる 1 パターンだけです。ルールの内容によっては変換のされ方が変わる可能性があります。
5.3. VPD で隠した行は読み取りに返らないが、setDoc は失敗する
VPD で隠した行は SDK の読み取りにも返りませんでしたが、その行に対する setDoc は主キー違反で失敗しました。監査証跡には、更新のたびに select document from ... where oid = :1 and PARENT_OID = :2 という 1行に絞る SELECT が UPDATE の直前に記録されています。ORDS 側がこの SELECT の結果で INSERT と UPDATE を切り替えており、VPD が既存行を隠したことで INSERT が選ばれたと考えられます。失敗したときの証跡そのものは取得していないため、そこは推測です。
これは VPD 一般の性質で、隠した行に対する書き込みが失敗するのは他のアプリケーションでも起こります。とはいえ「アプリのコードは変えずに DBA 側で行を絞れる」と考えて掛けると、アプリのエラーという形で出ます。読み取りだけを絞る用途と、書き込みも通す用途を分けて設計することになります。
6. まとめ
Oracle Backend with Firebase APIs でアプリを 1本作り、同じデータベースに DBA として接続して中を確認しました。
- SDK の関数名と引数は Firebase とほぼ同じで、
setDocと CEL の Security Rules だけで最小限のアプリが動く - 一方で使い始めるまでの準備は ORDS の管理者の仕事だった。公式手順に無い設定が 3つあり、無いと Console にもブラウザからも到達できない(7 章)
- コレクションの実体は JSON 型の列を持つ通常の表で、
SELECTで読み出せる。統合監査も VPD も SDK 経由の操作に対して動作するが、監査に残るのは接続に使われた 1つのデータベースユーザーだけである - Security Rules は CEL が SQL の等値比較に変換されて評価される。ただし判定はデータを読んだ後に実行されるため、監査証跡ではデータ取得の行だけを見ても許可と拒否を区別できない
アプリ開発者に渡す前に DBA 側で確認しておく項目を、今回の検証で洗い出しました。対象は認証・データ・Security Rules までで、ベクトル検索と App Trust(リクエストが正規のアプリから来たことを確かめる機能)は範囲外です。
7. 補足: セットアップでつまずいた箇所
ここからは、環境を用意するまでに詰まった点の記録です。同じ構成を作る方の参考になれば、という内容なので、読み飛ばしても本編には影響しません。
公式ドキュメントの手順3は次の流れです。
- データベース側の準備(
COMPATIBLEを 23.9.0 以上、MAX_STRING_SIZE=EXTENDED、TDE ウォレットの構成) -
ords --config <設定ディレクトリ> fusabase installで機能を有効化 -
OBAAS_ADMIN.OBAAS_ENABLE_SCHEMAでプロジェクト用スキーマを有効化 - ランディングページ
/ords/_/landingから Console を開いてサインイン
1 から 3 まではドキュメントどおりに終わりました。TDE は Free エディションでも設定できます4。3 の OBAAS_ENABLE_SCHEMA は SYS で実行すると ORA-06598 のエラーになることがあり、DBA ロールを持つ SYS 以外のユーザーで実行するようドキュメントの Troubleshooting に明記されています3。ここはドキュメントのとおりにしました。
コンテナのイメージによっては、1 の前提を満たしていないことがあります。今回使った gvenzl/oracle-free:23.26.2 は COMPATIBLE が 23.6.0 で、要件の 23.9.0 に届いていませんでした。CDB 側で引き上げて再起動しています。
-- CDB$ROOT で実行し、その後インスタンスを再起動する
ALTER SYSTEM SET COMPATIBLE='23.9.0' SCOPE=SPFILE;
MAX_STRING_SIZE の変更は、PDB を UPGRADE モードで開いて utl32k.sql を実行する手順です。TDE はウォレット用のディレクトリを作り、WALLET_ROOT と TDE_CONFIGURATION を設定してマスターキーを作ります。いずれも公式手順のとおりで詰まりませんでした3。
パスワードで保護したウォレットは、データベースを再起動すると閉じたままになります。この状態でアプリからサインインすると ORA-28365: Wallet is not open で失敗します。アプリユーザーの情報が暗号化された列に入っているためです。Console にはサインインできるのに、アプリからの認証だけが失敗します。再起動のたびにウォレットを開くか、自動ログインウォレットにしておきます。
問題は 4 でした。
7.1. Console の画面が表示されない
ランディングページに Backend for Firebase のタイルは出ます。押すとローディング表示のまま何も起きません。
ブラウザの開発者ツールを見ると、JavaScript と CSS が 404 でした。404 になっていたのは /ords/sql-developer/js/main-baas.js で、Console の静的ファイルは SQL Developer Web のパスから配信される作りになっています。そしてランディングページ上の SQL Developer Web は "App Unavailable" と表示されていました。
feature.sdw を有効にすると解決します。
ords --config /opt/ords-config config set feature.sdw true
無効に戻すと 301、有効にすると 200 が返ります。設定を 2回切り替えて同じ結果になることを確認しました。反映には ORDS の再起動が必要です。
一度 404 が返ると、ブラウザが恒久リダイレクト(301)をキャッシュし、設定を直した後も 404 が続きます。localhost の代わりに 127.0.0.1 で開くと別オリジン扱いになり回避できます。
7.2. SQL*Plus で接続できる資格情報でもサインインが 401
画面は表示されるようになりましたが、サインインは失敗します。ドキュメントは「fusabase_user の資格情報でサインインする」と書いており3、その資格情報で SQL*Plus からは接続できます。
スキーマ側の前提も満たしていました。DBA_ORDS_SCHEMAS に別名が ENABLED で登録済み、DBA_PROXIES に ORDS_PUBLIC_USER からのプロキシ接続の許可もあり、BAAS_ROLE も付与されています。それでも POST /ords/fusabase_user/sign-in/check は 401 を返し続けました。
ORDS のログを FINEST まで上げて、リクエスト単位のトレースを取りました。あわせて ORDS の war を確認したところ、原因は認証モードの決まり方にありました。restEnabledSql.active が未設定(デフォルトは false)だと、フォームに入力した資格情報を検証するコンポーネントがそもそも起動しません。パスワードが合っているかどうかに関係なく 401 になります。
ords --config /opt/ords-config config set restEnabledSql.active true
この 1行でサインインできるようになりました。あわせて、ランディングページの SQL Developer Web も "App Unavailable" ではなくなりました。
なお、401 のときのログには JWT 関連の例外(MissingDependencyException ... security.jwt.profile.audience)も出ますが、これはサインイン成功時にも同じように出ます。401 の原因ではありませんでした。
7.3. ブラウザから使うには配信元の登録が要る
もう 1つ、アプリを作ったあとに必要になった設定があります。Node.js から SDK を呼び出す構成では動いていましたが、同じコードをブラウザで開くと認証の時点で止まります。
Access to fetch at 'http://localhost:8080/ords/fusabase_user/_/baas-services/idm/onprem/.../authenticate'
from origin 'http://localhost:8000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
アプリを配信しているオリジンが許可されていないためです。Console のプロジェクト設定に Authorized domains というタブがあり、ここに登録すると通るようになります。入力形式はスキームを含む形(http://localhost:8000)で、localhost:8000 では登録できませんでした。
ORDS 側にも CORS 用の設定(security.externalSessionTrustedOrigins)がありますが、こちらを設定しても Backend for Firebase のエンドポイントには反映されませんでした。プロジェクト単位の設定として、Console のプロジェクト設定に登録する必要があります。
7.4. 必要だった設定のまとめ
| 設定 | 場所 | 未設定だと起きること | 公式ドキュメントの記載 |
|---|---|---|---|
feature.sdw=true |
ORDS 設定 | Console の静的ファイルが 404 になり画面が表示されない | なし |
restEnabledSql.active=true |
ORDS 設定 | 有効な資格情報でもサインインが一律 401 になる | なし |
| Authorized domains | Console のプロジェクト設定 | ブラウザからの呼び出しが CORS で止まる | なし |
上 2つは ORDS 側の設定で、データベースの構成やスキーマの権限とは無関係です。「Firebase のように手軽に」と紹介されるツールキットですが、使い始めるまでの準備は ORDS の管理者の仕事でした。
参考
そのほか、公式のランディングページ5と、iOS / Android / Web それぞれの無料ワークショップ678が公開されています。
-
Introducing Oracle Backend with Firebase APIs: Build Mobile and Web Apps on Oracle AI Database(Oracle Database Blog、2026 年 5 月 14 日) ↩ ↩2 ↩3
-
Oracle Backend for Firebase Developer's Guide, 2 Fundamentals(2.1.2 Oracle-Specific Terms の Java Parser (Security Rule)、2.3 Backend Overview) ↩ ↩2 ↩3
-
Oracle Backend for Firebase Developer's Guide, 3 Installing and Configuring(3.2 Prerequisites、3.6 Open the Console、3.10 Troubleshooting) ↩ ↩2 ↩3 ↩4
-
Simplified TDE configuration in Oracle Database 23ai Free(Oracle Developers Blog) ↩






