学習・技術検証用デモ — 本システムは実際の銀行サービスではありません。

公開情報をもとに構築した教育目的のシステムです。実在する個人情報・口座情報・機密情報・認証情報等を入力しないでください。

ミニ銀行ACADEMY SYSTEM LEARNING
教材メニュー
学習進捗0 / 134
あと134項目
ADVANCED · 発展COURSE 03 · BANKING SYSTEM

Security Architecture

認証だけに頼らず、API・権限・RLS・非公開schema・DB関数を重ねる多層防御を学びます。

01 / 08
BANKING SYSTEM · 銀行システム

台帳、振込、KYC、権限、監査、照合をMinibankの実装と結び付ける。

先に学ぶ内容 — Prerequisite: 基礎 / 銀行業務

学ぶこと — LEARNING GOALS

このページで学ぶこと

  • AuthenticationとAuthorizationの違い
  • RLSとSECURITY DEFINER
  • Default Denyと権限最小化

体験05 · SECURITY(多層防御)

ログインだけで、何が見えるか確かめよう

まずは予想をひと言。本文を読んだ後、自分に見える範囲と、記録される操作を確認します。

体験学習 · STEP 1 / 5
  1. 01
    まず予想する — PREDICT

    まずここだけ:思ったことをひと言書く

    ログインさえできれば、他の利用者の口座情報も見えると思いますか?

    正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。

    ヒント1を見る

    「本人であること」と「その操作をしてよいこと」は、同じ確認でしょうか?

    ヒント2を見る

    画面にボタンが無いことと、そのボタンの処理を実行できないことは、同じでしょうか?

    もっと詳しく:この問いで使う専門用語
    • Authentication(認証)は、あなたが誰かを確認することです。
    • Authorization(認可)は、あなたがその操作をしてよいかを確認することです。
    • RLS(Row Level Security、行単位の閲覧制限)は、他人のデータを見せない規則です。
    • Audit(監査記録)は、誰が、いつ、何をしたかを残す記録です。
まずはひと言だけで大丈夫です。

この後の本文を最後まで読んでから、実際に操作して確かめるステップへ進みます。

日常語で理解する — PLAIN LANGUAGE

まず、ここだけつかむ

1

セキュリティは1つの鍵では守れません。本人確認、操作権限、行単位の閲覧制限、関数の実行権限、DB制約を重ねます。

2

認証は『誰か』の確認、認可は『その人がこの操作をしてよいか』の確認です。ログイン済みでも他人の口座は操作できません。

3

限られた処理にだけ、一時的に強い権限を貸し出す仕組みがあります。入口・実行者・検索先・公開範囲のどこかを誤ると、意図しない権限の拡大につながります。この考え方を「SECURITY DEFINER」と呼びます。

順番に理解する — DIFFICULT PARTS, STEP BY STEP

難しいポイントを、順番にほどく

専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。

01

認証と認可

やさしく言うと

認証は本人確認、認可は対象と操作の許可確認です。

なぜ必要?

ログイン済みというだけで管理操作や他人のデータ操作を許す事故を防ぎます。

Minibankでは

Authがuidを確定し、RLSやRPCが所有者・role・active状態を別途確認します。

02

RLS

やさしく言うと

同じtableの中でも、利用者ごとに見える・変更できる行をDBが絞る仕組みです。

なぜ必要?

APIで検索条件を付け忘れても、他人の行をDBから返さない最後の境界になります。

Minibankでは

auth.uid()とcustomerの対応を使い、本人の顧客・口座・取引だけを許可します。

03

SECURITY DEFINER

やさしく言うと

呼び出した人ではなく、関数所有者の権限で決められた処理を行うDB関数です。

なぜ必要?

private tableを直接公開せず金融処理を実行できますが、認可不足なら強権限の代理実行になります。

Minibankでは

公開wrapperの中でcaller identityとroleを確認し、private schemaの処理へ接続します。

04

search_pathとdefault deny

やさしく言うと

search_pathは名前だけでtable/functionを探す順番、default denyは明示許可しない限り拒否する方針です。

なぜ必要?

攻撃者が同名objectを先に見つけさせたり、PUBLICの初期権限から関数を実行したりする危険を減らします。

Minibankでは

definer関数のsearch_pathを固定し、PUBLIC EXECUTEをrevokeして必要roleだけgrantします。

05

安全なerror境界

やさしく言うと

内部の詳しいerrorと、利用者へ見せるmessageを分けます。

なぜ必要?

table名、constraint、SQL断片などを攻撃の手掛かりとして漏らさないためです。

Minibankでは

RPCはSQLSTATEを返し、mapRpcError()が許可済みの日本語へ変換し、未知codeは一般errorへ畳みます。

発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。

用語から実装へ

用語なぜ必要かMinibank
Authentication誰かを確認するSupabase AuthのgetUser()とauth.uid()
Authorization何をしてよいか決めるRLS、admin role、RPC内の状態検査
RLS行単位で他人のデータを隠すcustomersとbank_accountsのown-row policy
SECURITY DEFINER限定処理だけを所有者権限で実行public wrapperからprivate schemaへ委譲
PUBLIC EXECUTE関数の暗黙公開を防ぐREVOKE後に必要roleへだけGRANT
Default Deny追加許可がない限り拒否private tableはzero policy・zero privilege
Schema separationAPIから内部オブジェクトを隔離ledger/core/admin/audit/reconciliation
Privileged operation高権限操作を狭い入口へ集約active operator確認付きadmin RPC

直接RPCを呼ばれても守る

UIのボタン非表示は認可ではありません。顧客RPCは `auth.uid()` から呼出者を再解決し、管理RPCは `admin.admin_operators` のroleとstatusをDB内で確認します。`search_path = ''` とschema-qualified参照は、同名オブジェクトを悪用した解決先のすり替えを抑えます。

OTP・振込先も、tableを直接公開しない

transfer_requests、transfer_otp_challenges、transfer_beneficiariesはRLSを有効にしたうえで、anon・authenticated・service_roleのtable権限をすべてrevokeしています。顧客はSECURITY DEFINER RPCだけを通り、各関数がauth.uid()とownerを確認します。

OTP平文はDBへ保存せず、開発環境のdemo表示だけに限定します。production API responseには含めません。お気に入り登録では銀行・支店・口座名義をclient入力にせず、DBが実口座から解決します。

実バグ:内部DBエラーを利用者へ見せない

生のmessageにはtable名、constraint名、SQL断片、内部状態が含まれる可能性があります。server内部の診断情報と、利用者へ伝える安全なmessageは分けます。Minibankではraw messageをprivate transfer_requests.failure_reasonへ診断用として残し、customer-facing responseにはSQLSTATEをmappingした文言だけを返します。

  1. 01execute_transfer()内部でPostgreSQL error
  2. 02旧実装はmessage_textをfailure_reasonとしてRPC結果へ格納
  3. 03APIがその文字列をclientへ返し得た
  4. 04Migrationで返却列をfailure_code(SQLSTATE)へ変更
  5. 05TransferServiceがmapRpcError()でallowlist済み日本語へ変換
  6. 06未知codeは一般的なINTERNAL_ERRORへ畳む

実際のMinibankでは

  • anon・authenticated・service_roleのtable/function権限をmigrationで明示
  • service_roleのcustomers/bank_accounts直接DMLをrevoke
  • adminとcustomerのactive actor兼務をcustomer INSERT trigger・admin作成RPC・ACTOR_COLLISION検査で防止
  • OTP request/challengeとお気に入りtableへ直接Data API権限を与えない
  • SQLSTATEをAPIの一般化された応答へmappingし、口座列挙やDB詳細漏えいを抑制
  • privilege regressionが公開関数ACL、table privilege、RLS、公開schemaを確認

体験学習の続き · LEARNING LOOP

ここまで読めましたか?続けて確認してみましょう

認証と認可の違いを読んだところで、実際の画面と監査記録を見比べ、自分の言葉で説明してみます。

STEP 2〜5 / 5
  1. 02
    実際に観察する — OBSERVE

    同じ仕組みを、動く画面から観察する

    まず顧客画面を開いて、自分に見える範囲を観察し、この教材へ戻ります。リンクは新しいタブで開きます。ログインが必要です。

    アカウントがない場合は新規登録が必要です。本人確認・口座開設には管理者承認が必要です。承認前や操作できない場合も、この教材内の図・説明・Source Trail(教材の参照元)で代替学習し、次へ進めます。

    ログイン・新規登録の入口を開く ↗操作できない場合:教材の参照元で確認する ↓
    もっと詳しく:管理者の画面で観察する

    管理者ログインが必要です。権限がない場合は、次の「証拠を確認する」の教材による確認で進められます。

  2. 03
    証拠を確認する — EVIDENCE

    何を見たか、1つずつ確かめる

    Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。

    画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。

  3. 04
    コードの処理経路を追う — CODE TRAIL

    コードを書く必要はありません

    ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。

    基本の順序は、画面 → API Route(依頼を受ける窓口)→ TransferService(処理をつなぐサービス)→ DB / RPC(記録の保存場所と、その処理の呼び出し)です。まず最初の2つを見て、続くサービスは「もっと詳しく」で確認します。

    API Route(依頼を受ける窓口)で、Bearer tokenから本人を確認する処理。まず「誰か」を確かめる箇所を探します。

    src/lib/auth/getAuthContext.ts

    Migration(DB定義の変更履歴)。他人の行を見せないための規則(RLS)の定義を確認します。

    supabase/migrations/20260819132835_customers_rls_and_privileges.sql
    教材の参照元を確認する — Source Trail ↓
  4. 05
    自分の言葉で説明する — EXPLAIN

    LEVEL 1 — やさしい説明(まずここだけ)

    ログインできることと、ある操作をしてよいことは、なぜ別の確認が必要なのでしょうか? 短い2文を目安に説明してください。

    日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。

    ヒント1を見る

    「認証」と「認可」という2つの言葉を使って書いてみましょう。

    ヒント2を見る

    もし画面のボタンを消すだけで安全になるなら、何が困るか考えてみましょう。

    もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)

    Authentication、Authorization、RLS、SECURITY DEFINERの4つの役割を結び付けて説明してみましょう。

    挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。

    説明の確認ポイント — Checkpoint
    • 認証と認可が別の確認であると説明できる
    • 画面の制限とDB側の制限が別の防御線であると説明できる
予想5文字以上・証拠3項目・説明20文字以上で完了できます。

証拠の確認:0 / 3。LEVEL 2(専門用語での説明)は任意です。

入力はこのブラウザに保存されます。Academy教材の進捗とは別の学習メモです。

理解を確かめる — CHECK YOUR UNDERSTANDING

4問で理解チェック

答えを頭の中で考えてから、各項目を開いて確認してください。

Q1ログイン済みなら認可も完了していますか?
答え

いいえ。誰かは分かっても、その口座や管理操作への権限があるとは限りません。

Q2RLSがあればSECURITY DEFINER内の認可確認は不要ですか?
答え

不要ではありません。definer関数は強い所有者権限でRLSを迂回し得るため、関数自身の確認が重要です。

Q3UIで管理ボタンを隠すことは認可ですか?
答え

違います。操作性の制御であり、API/RPCを直接呼ぶ攻撃はDB側の認可で止めます。

Q4DBのmessage_textをそのまま画面へ出してよいですか?
答え

いけません。安定したcodeを安全な公開messageへ変換し、内部詳細は権限を限定した診断情報として扱います。

ここまで読めましたか?

完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。

教材の参照元 — SOURCE TRAIL

根拠と関連教材

CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。

確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。

  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260819132835_customers_rls_and_privileges.sql顧客RLSとcolumn privilege
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823090001_revoke_service_role_direct_dml_on_customers_and_bank_accounts.sqlservice_role直接DMLの縮小
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823160001_enforce_actor_separation.sql顧客・active adminの役割分離
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823170001_create_transfer_beneficiary_and_otp.sqlOTP tableとRPCの権限境界
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823170004_sanitize_confirm_transfer_otp_failure_reason.sql生DB error漏洩の修正
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823190001_create_transfer_beneficiaries.sqlowner-only振込先RPC
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/lib/errors/rpcErrors.tsmapRpcError()SQLSTATEから安全な公開messageへの変換
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/suites/privilege.mjsACLとschema公開設定の回帰
  • 過去の実装 — HISTORICAL IMPLEMENTATIONscripts/regression/suites/transferOtp.mjs#128以前のデモOTP前提の回帰(OTP ownershipとraw error非漏洩)