デモアプリのラマ銀行をDioxusとaxumで書き直す
ラマ銀行 https://qiita.com/tnagata/items/f65459a129d5d01561a8.md をフロントエンドにDioxus, バックエンドにaxumを使って書き直した
開発環境: Windows 11 Home, VS Code, git-bash, Dioxus (v. 0.7.2), axum (v. 0.7.9)で確認
アプリの実行イメージ
アプリの仕様概略
- ホームページ: 銀行からのお知らせやイベント案内を表示。上部に新規口座開設(登録)とログインボタン
- 口座登録: ユーザー名、パスワード、氏名、口座番号(アルファベット1文字 + 4~6桁数字)、初期残高を入力
- ログイン: ユーザー名とパスワードで認証。失敗時はエラーメッセージを表示
- ログイン後の機能: 残高照会(初期ページ)、送金、取引履歴、登録情報、本人確認写真のアップロード
- 送金: 送金先口座番号と金額を入力。残高不足時は警告を表示
- 残高照会
- 取引履歴: 過去1ヶ月の入金・送金履歴を表示
- 登録情報 パスワードの変更、本人写真
- 本人確認写真 本人写真のアップロード
📂 ソースファイルの役割・機能(1行要約)
Backend (axum079)
- main.rs アプリケーションの起動エントリポイント。ロギングの設定や、サーバーのポート(3001)待機、グレースフルシャットダウンを管理する
- lib.rs アプリケーションの核となる設定。ルーティング定義、データベース(sqlite3)接続の初期化、CORS設定、静的ファイルの配信設定を行う。
- error.rs アプリケーション全体で使用するカスタムエラー(AppError)の定義。各エラーを適切なHTTPステータスとJSONレスポンスへ変換する。
- models.rs データベースのテーブル(User, Account, Transactionなど)に対応する構造体と、APIのリクエスト/レスポンス用データモデルを定義する
- handlers/accounts.rs 口座情報の取得を担当。指定された口座番号に基づき、残高などの情報をデータベースから取得して返す
- handlers/auth.rs 認証基盤を担当。ユーザー登録、ログイン(JWT発行)、パスワード変更、ログアウトのロジックを実装している
- handlers/transactions.rs 取引明細の取得を担当。特定の口座に関連する入出金履歴をデータベースから検索し、一覧として返す
- handlers/transfer.rs 送金処理のメインロジック。DBトランザクションを用いて、自分と相手の残高更新と取引履歴の作成をアトミックに実行する
- handlers/upload.rs プロフィール画像のアップロード管理。マルチパートリクエストから画像を抽出し、ファイル保存とDBパスの更新を行う
- handlers/user.rs │ ユーザープロフィールの取得を担当。ログイン中ユーザーが自分や他者の基本情報を参照するためのエンドポイントを提供する
- middleware/auth.rs 認証ミドルウェアの実装。HTTPヘッダーのBearerトークンを検証し、認証済みユーザー情報を各ハンドラーへ抽出・提供する │
Frontend
-
main.rs アプリケーションの開始点。ロガーの初期化、認証プロバイダーの設定、メインAppコンポーネントの定義を行っている
-
routes.rs アプリケーションのルーティング(画面遷移)を定義。各ページへのパス設定と、共通レイアウトであるナビゲーションバー(NavBar)の構成を管理している
-
components/navigation.rs 画面上部に表示されるナビゲーションバー。ログイン状態に応じたメニュー(残高照会、振込、ログアウトなど)の表示切り替えを制御する
-
contexts/auth 認証状態(ログイン中のユーザー情報)を管理。LocalStorageを利用したログイン情報の保存・復元や、ログイン/ログアウト処理のロジックを保持している
-
pages/(各画面のUI)
- home.rs トップページ。銀行の紹介やマスコットキャラクターの表示、ログイン/新規登録への誘導を行う
- login.rs ログイン画面。ユーザー名とパスワードの入力フォームと認証処理
- register.rs 新規口座開設(ユーザー登録)画面
- balance.rs 残高照会画面。現在の口座残高を表示
- transfer.rs 振込画面。振込先口座番号や金額を入力して送金処理を行う
- history.rs 入出金明細画面。過去の取引履歴を一覧表示
- profile.rs プロフィール管理画面。登録情報の表示や更新
- upload.rs 本人確認書類やアイコンなどの画像アップロード画面
-
services/api.rs バックエンド(Axum)とのHTTP通信を共通化。GET/POSTリクエストの送信や、認証トークン(BearerToken)の自動付与を担う
-
types/(データ型定義)
- user.rs ユーザー情報やログインレスポンスのデータ構造
- account.rs 口座情報に関するデータ構造
- transaction.rs 取引履歴(入出金)に関するデータ構造
🔄 処理フローの詳細解説を2例
①フロントエンドからバックエンド、データベースを経てJWTが返されるまでのログイン処理のフローを解説する:
(1) フロントエンド:ユーザー入力と送信
ユーザーがログインボタンをクリックすると、Dioxus側で処理が始まる
- Login コンポーネント (dioxus072/src/pages/login.rs)
- handle_login (クロージャ): フォーム送信時に実行され、入力された username と passwordをJSON形式にまとめ、APIサービスを呼び出す
- request_post 関数 (dioxus072/src/services/api.rs)
- reqwest ライブラリを使用して、バックエンドの /auth/login エンドポイントに対してPOSTリクエストを送信する
(2) バックエンド:リクエストの受信とルーティング
Axumサーバーがリクエストを受け取り、適切なハンドラに振り分ける
- create_app / auth_routes (axum079/src/lib.rs)
- ルーティング定義により、POST /auth/login リクエストが handlers::auth::login 関数にルーティングされる
(3) バックエンド:認証ロジックとDB照合
バックエンドのハンドラがデータベースの情報を確認し、ユーザーを認証する
- login ハンドラ (axum079/src/handlers/auth.rs)
- DB照合: sqlx::query_as を使用して、SQLiteデータベース(users
テーブル)からユーザー名に一致するレコードを検索する - パスワード検証: bcrypt::verify
関数を使い、ユーザーが入力した平文パスワードと、DBに保存されているハッシュ化されたパスワードを照合する*1(更に詳しくは下記参照) - 認証失敗時の処理: ユーザーが見つからない、またはパスワードが不一致の場合は、AppError::Unauthorizedを返す
- DB照合: sqlx::query_as を使用して、SQLiteデータベース(users
(4) バックエンド:JWTの発行と返却
認証に成功すると、トークンを生成してクライアントに返す
- login ハンドラ (続き)
- Claims(クレーム)の作成: ユーザーIDと有効期限(24時間後)を含む Claims 構造体を作成する
- JWT生成: jsonwebtoken::encode 関数を使用し、サーバー側の秘密鍵(コード内では"secret")で署名してJWT(トークン文字列)を生成する
- レスポンス送信: 生成されたトークンとユーザー情報をRegisterResponse構造体に詰め込み、JSON形式でフロントエンドに返す
(5) フロントエンド:トークンの保存
フロントエンドは受け取ったトークンを以後のリクエストのために保持します。
- handle_login (続き) & AuthState::login (dioxus072/src/contexts/auth.rs)
- バックエンドから成功レスポンスを受け取ると、auth.login(data) が呼ばれる
- トークンを含むユーザー情報が LocalStorage に保存され、アプリ内のグローバルな状態(AuthState)が更新される
- これにより、次回以降のAPIリクエスト(残高照会など)では、request_post/get 関数が LocalStorage
からトークンを読み出し、HTTPヘッダーに自動的に付与するようになる
*1:
-
DBに保存されている「ハッシュ」の中身
DBの password_hash
カラムに保存されている文字列(例:$2b$12$tQ...)は、単なるハッシュ値ではなく、以下の情報がセットになっている- アルゴリズムの種類 (例: 2b)
- コスト(計算回数) (例: 12)
- ソルト(ランダムなデータ):ハッシュ化する際に追加された「塩」
- ハッシュ値本体
-
bcrypt::verify(平文, DBのハッシュ) の内部動作
この関数を呼び出すと、内部で以下のステップが実行される- 情報の抽出: 第2引数で渡された「DBのハッシュ文字列」から、当時のソルトとコストを抜き出す
- 再ハッシュ化: 抜き出した同じソルトと同じコストを使って、ユーザーが今入力した「平文パスワード」をハッシュ化する
- 比較: 「今計算して作ったハッシュ」と「DBに保存されていたハッシュ本体」が一致するかをチェックする
②取引履歴表示ついて、フロントエンドからDB、そして画面にデータが表示されるまでのフローを解説する:
(1) フロントエンド:リソースの要求
ユーザーが「取引履歴」ページを表示した際、Dioxusのリアクティブな仕組みによって自動的にデータ取得が始まる
- History コンポーネント (dioxus072/src/pages/history.rs)
- use_resource: このフックを使用して、ページ読み込み時に非同期処理を開始する
- ログイン中のユーザー情報(AuthState)から「自分の口座番号」を取得し、APIを呼び出す
- request_get 関数 (dioxus072/src/services/api.rs)
- LocalStorage に保存されているJWT(トークン)を読み出し、HTTPヘッダーの Authorization: Bearer <トークン>にセットする
- GETリクエスト /api/history/<口座番号> をバックエンドに送信する
(2) バックエンド:認証とルーティング
リクエストがバックエンドに到達すると、ミドルウェアがセキュリティをチェックする
- AuthUser ミドルウェア (axum079/src/middleware/auth.rs)
- HTTPヘッダーのJWTを検証し、有効であればリクエストを続行させます。無効であればここでエラーを返す
- get_history ハンドラ (axum079/src/handlers/transactions.rs)
- /api/history/:account_number に対応するこの関数が実行される
- パスパラメータから account_number を受け取る
(3) バックエンド:DB操作
実際にデータベースからデータを抽出する
- SQLクエリの実行
- sqlx::query_as を使用し、transactions テーブルに対して検索をかける
- 条件: from_account(送金元)または to_account(送金先)が、自分の口座番号と一致する全レコードを取得する
- 並び順: ORDER BY created_at DESC により、新しい取引が上に来るように並び替える
(4) バックエンド:レスポンス送信
取得したデータをフロントエンドが扱いやすい形式に変換して返す
- JSON化
- 取得した Vec をJSON配列に変換し、フロントエンドにレスポンスとして返す
(5) フロントエンド:UIへのレンダリング
データが戻ってくると、Dioxusが自動的に画面を更新する
- History コンポーネント (続き)
- history_resource.read() の中身が「取得済みデータ」に変わる
- 送金/入金の判別: 自分の口座番号と from_account
を比較し、一致すれば「送金(赤字)」、そうでなければ「入金(緑字)」として処理する - 日時の整形: chrono ライブラリを使用して、DB内のUTC時間をローカル時刻(日本時間など)のフォーマット(2026/04/29
...)に変換する - テーブル表示: rsx! マクロにより、取引の一覧がHTMLの
要素として描画される
🚀 試してみたい方へ
GitHub リポジトリ: https://github.com/tnagata/llamabank-rust
- リポジトリをクローン
-
README.mdの指示に従ってbackend(axum079)とfrontend(dioxus072)を起動 -
esato / esato123でログインして動作確認! - 次に
- 新規登録も試して!
おまけ:
故意に dangerous_inner_htmlを使ってメモを表示している箇所があり、ここには Stored XSS(格納型クロスサイトスクリプティング)の脆弱性がある。演習として自分で修正してください。

