anTech

News

Webアプリのデモ環境を安全に公開する方法 — 読み取り専用モードの設計と公開前チェックリスト【vol.4/全4回】

開発ブログ

この記事は、自作のWebアプリやお客様への納品物を「実際に触れるデモ」として公開したいエンジニアの方と、システム導入を検討していて「契約前に実物を触って確かめたい」事業者の方に向けて書いています。デモ環境は便利な反面、何も対策せずに公開すると事故のもとです。本記事では、読み取り専用モード(DEMO_MODE)の設計からサンプルデータの作り方、毎朝の自動リセット、公開前チェックリストまで、実際に踏んだ落とし穴と一緒に解説します。先にデモを触りたい方はデモのURL・アカウント情報へ

訪問看護のシフト管理システムを自作した記録としては、これが最終回です。vol.1(Excelシフト表の限界)vol.2(データベース設計)vol.3(カレンダーUIの自作)と作ってきたシステム「YORISOI」を、誰でも触れる公開デモとしてお披露目します。

YORISOIの公開デモ、はじめました

まずはお知らせから。この連載で作ってきたシフト管理システムの公開デモを、本記事をもって正式公開します。

YORISOI 公開デモ

https://yorisoi.ansungtechnology.work/

  • 管理者アカウント: admin / スタッフアカウント: staff
  • パスワードは共通: antech01234!

表示されるのはすべて架空のサンプルデータです。読み取り専用のため登録・変更はできません。データは毎朝3:15に自動リセットされます。

シフトのドラッグ&ドロップ(vol.3で作ったカレンダーUI)、スタッフ・利用者の管理画面、ダッシュボードなど、連載で紹介してきた画面をそのまま操作できます。スマホでも動きます。機能の全体像はYORISOIの紹介ページにまとめています。

……と、URLを貼るだけなら一瞬なのですが、ここに至るまでに「安全に試してもらう」ための仕組みをいくつも作り、しかも告知直前の実地テストでその安全装置が動いていないことを発見して青くなりました。ここからが本題です。

デモ環境を無防備に公開すると何が起きるか

ログインIDとパスワードを公開する以上、デモ環境には「世界中の誰でも管理画面に入れる」状態が生まれます。対策なしで公開した場合に起きうることを挙げると:

  • データの破壊 — 悪意がなくても、試しに削除ボタンを押す人は必ずいます。次の見学者には壊れた画面が見えます
  • 本物の個人情報の混入 — 「入力できてしまう」と、誰かが実在の利用者名や電話番号を登録するリスクがあります。訪問看護のような医療・介護系の分野では特に致命的です
  • メールの誤送信 — スタッフ登録時に招待メールを送る仕様なら、デモで入力された実在アドレスに本物のメールが飛びます
  • 同時利用の競合 — 複数人が同時に触ると、お互いの操作でデータが変わって見え、アプリの不具合だと誤解されます

YORISOIでは、これらを「読み取り専用モード」「サンプルデータの自動リセット」「メール送信の抑止」の3層で防ぐ設計にしました。順に見ていきます。

デモ環境を安全に公開する3層構えの図解。第1層は読み取り専用モード(DEMO_MODE)、第2層はサンプルデータと毎朝の自動リセット、第3層は環境の分離と抑止
デモ訪問者とデモ専用DBの間に置いた3層の安全装置

設計1: 読み取り専用モード(DEMO_MODE)

核になるのは、環境変数ひとつでアプリ全体を読み取り専用に切り替える仕組みです。.env にフラグを1行足すと:

CI_ENVIRONMENT = production
DEMO_MODE = true

CodeIgniter 4のフィルター(すべてのリクエストの前に挟まる処理)が、書き込み系のリクエストを一括でブロックします。実装の骨子はこれだけです:

class DemoReadonlyFilter implements FilterInterface
{
    // デモモードでもブロックしないPOST
    private array $allowedPosts = [
        'login/authenticate', // など認証系のみ
    ];

    public function before(RequestInterface $request, $arguments = null)
    {
        if (! env('DEMO_MODE')) {
            return; // 通常環境では何もしない
        }

        // HTTPメソッド名で書き込み系だけを遮断する
        if (! in_array(strtolower($request->getMethod()),
                ['post', 'put', 'patch', 'delete'], true)) {
            return; // GETなどの読み取りは素通し
        }

        // ログイン・ログアウトは許可
        $uri = trim($request->getUri()->getPath(), '/');
        if (in_array($uri, $this->allowedPosts, true)) {
            return;
        }

        // AJAXにはJSONで、フォーム送信にはリダイレクトで断る
        if ($request->isAJAX()) {
            return service('response')
                ->setJSON(['success' => false,
                           'message' => 'デモ環境のため、データの変更はできません。'])
                ->setStatusCode(403);
        }
        return redirect()->back()
            ->with('error', 'デモ環境のため、データの変更はできません。');
    }
}

ポイントは3つあります。

コントローラーではなくフィルターで守る

「削除ボタンを押されたら困るから、削除処理にチェックを足そう」という発想で個別のコントローラーにif文を足していくと、必ず漏れが出ます。画面は今後も増えるからです。リクエストの入口でHTTPメソッドそのものを見て遮断すれば、将来追加する画面も自動的に保護されます。守る対象を「機能の一覧」ではなく「書き込みという行為」で定義するのがコツです。

ログインだけは通す

ログインもPOSTリクエストなので、単純に全部塞ぐとデモに入れなくなります。認証系のルートだけ明示的に許可リストに載せます。逆に言うと、許可リストに載っていないPOSTは新機能でも何でも自動でブロック側に倒れる、安全側の設計です。

断り方を2種類用意する

通常のフォーム送信には「元の画面に戻してメッセージ表示」、JavaScriptからのAJAX通信(シフトのドラッグ&ドロップ保存など)には「403 + JSONメッセージ」。同じブロックでも、リクエストの種類に合った形で返さないと、画面が白くなったり無言で失敗したりして「壊れたアプリ」に見えてしまいます。

落とし穴①: その安全装置、本当に動いていますか

ここからが実話です。実はこのデモ、URL自体は1週間ほど前からサーバー上で静かに稼働していました。この記事で告知する前の最終チェックとして、Playwright(ブラウザ操作を自動で再生するテストツール)で「ログイン → 利用者を登録してみる → ブロックされることを確認」という実地テストを流しました。結果は——

login OK?: true
block message?: false
record leaked?: true   ← 登録できてしまった
RESULT: FAIL

書き込みブロックが、まったく効いていませんでした。実装済みのはずのフィルターは存在し、DEMO_MODEもtrue。それでも利用者が普通に登録できてしまう。告知前に気づけたのは幸運でしたが、その間デモは無防備なまま公開されていたわけで、これは本記事でいちばん白状しにくい失敗です。

原因はフレームワークの仕様変更でした。このフィルターを最初に書いた時点のCodeIgniterでは$request->getMethod()が小文字(post)を返していたのですが、4.5以降は大文字(POST)を返す仕様に変わっていたのです。フィルター内の比較は!== 'post'。大文字のPOSTはこの条件に合致せず、「書き込みではない」と判定されて素通りしていました。

怖いのは、これがエラーを一切出さずに壊れるタイプの不具合だということです。例外も警告も出ません。アプリは完璧に動きます——守られていないだけで。フレームワークのバージョンアップで安全装置だけが静かに外れる。この経験からの教訓は明確です:

  • 「実装した」と「動いている」は別物。安全装置は、破ろうとするテストとセットで初めて完成する
  • ブロック処理のような否定形のロジックは、成功時ではなく失敗時の挙動をテストする(「登録できないこと」を確認する)
  • 文字列比較は仕様変更に弱い。strtolower()を挟む、定数を使うなど、大文字小文字に依存しない書き方をする

修正後のテストは無事にPASSし、ついでに「ブロックのメッセージが一部の画面でしか表示されない」問題も見つかったので、メッセージ表示を共通レイアウトに一元化しました。デモの訪問者が保存を押して無言でフォームに戻されるのは、「壊れている」ようにしか見えませんから。——ちなみに、冒頭のコード例にあったstrtolower()は、この失敗の傷跡です。

修正後のデモ画面。利用者新規追加フォームで保存しようとすると「デモ環境のため、データの変更はできません。」というメッセージが表示されている
修正後: 登録しようとすると理由つきでブロックされる(画面はサンプルデータ)

設計2: サンプルデータと毎朝の自動リセット

デモに入って白紙の画面が出てきたら、何のソフトか伝わりません。かといって本物のデータは1件たりとも使えません。YORISOIではシーダー(サンプルデータ投入スクリプト)を1本用意し、これをcron(サーバーの定時実行機能)で毎朝再実行することで「常にきれいな見本」を保っています。

# 毎朝3:15にデモデータをリセット
15 3 * * * cd /path/to/app && php spark db:seed DemoSeeder

シーダーの設計で意識した点:

  • 明らかに架空とわかるデータにする — 組織名は「サンプル訪問看護ステーション」、電話は03-0000-XXXX、メールは@demo-care.example。実在の人名・住所・番号は使いません
  • 「今日」を基準にシフトを生成する — 固定日付でデータを作ると、デモを開くたびにカレンダーが過去の週を表示する「化石デモ」になります。YORISOIのシーダーは実行日を基準に前後の週へシフト216件を配置するので、いつ開いても「今週の現場」が見えます
  • 全テーブルを空にしてから入れ直す — 差分更新にせず毎回TRUNCATE(全件削除)して作り直します。読み取り専用モードがあるので基本的に荒らされようがないのですが、仮に何かの拍子でデータが変わっても、翌朝には必ず初期状態へ戻ります

設計3: メール抑止・DB分離などその他の安全装置

  • メール送信の抑止 — DEMO_MODEがtrueの間は、スタッフ登録時の招待メールやパスワードリセットメールの送信をスキップします。スタッフ登録は読み取り専用モードでも止まりますが、パスワードリセットの申請はログインのために許可しているルートなので、リセットメールに関してはこの抑止が実質の防波堤です
  • サンプルデータバナー — 全画面の最上部に「この画面に表示されている内容は全て、サンプルデータとなります」というバナーを常時表示。スクリーンショットが一人歩きしても、架空データであることが画像の中に残ります
  • デモ専用のデータベースと専用ユーザー — 本番系のDBとは物理的に分離し、デモ用DBだけに権限を持つDBユーザーで接続します。仮にアプリ側に穴があっても、被害はデモ用DBの中で止まります
  • 役割の違う2アカウント — 管理者(admin)とスタッフ(staff)を用意。権限による画面の違いもデモで確認できます
YORISOIデモのダッシュボード画面。最上部に赤い帯で「この画面に表示されている内容は全て、サンプルデータとなります」というバナーが常時表示されている
全画面の最上部にサンプルデータバナーを常時表示

落とし穴②: デモ環境の新規構築は最高の結合テスト

もうひとつ、デモ環境の構築中に見つかった問題を白状します。サーバーに新しいデータベースを作り、いつものphp spark migrate(テーブル定義の一括構築)を実行。マイグレーション自体は問題なく完走しました。ところが続けてサンプルデータを投入すると——シーダーが「そんなテーブルは存在しない」というエラーで止まりました。開発中のDBには確かに存在するテーブルが、マイグレーション(ゼロから作るための手順書)には書かれていなかったのです。

原因は開発の歴史そのものでした。開発初期に手書きのSQLで足したテーブル、機能追加のたびに直接ALTERで加えたカラム。動いているDBは正しく育っていく一方、「ゼロから作る手順書」の方は更新されず、両者が静かに乖離していました。開発マシンのDBは一度も作り直さないので、乖離は新規構築の日まで発覚しません

今回はデモ構築がその「新規構築の日」になり、開発DBの実スキーマとマイグレーションを突き合わせて、不足していた中間テーブル2つ・カラム4つ・お知らせテーブル1つを手順書側に反映しました。おかげで今は、まっさらなサーバーにspark migrate一発で環境を再現できます。デモ公開には「第三者の目で自分のアプリを見る」効果がありますが、それ以前に構築作業そのものが、開発環境に隠れていた技術的負債をあぶり出す結合テストでした。

デモサイト公開前チェックリスト11項目

今回の作業を11項目のチェックリストにまとめました。Webアプリのデモサイトを公開する方はご自由にお使いください。

  • 本番モードで動作している(デバッグ情報・詳細エラーが画面に出ない)
  • HTTPSが有効
  • デモ専用DBと専用DBユーザーに分離した
  • 読み取り専用モードが有効で、「登録できないこと」を実際に試して確認した
  • ブロック時に人間に伝わるメッセージが表示される(フォーム/AJAX両方)
  • サンプルデータが実在の情報を含まない
  • データの自動リセットが設定されている
  • メール等の外部送信が止まっている
  • サンプルデータである旨が画面に常時表示される
  • デモ用アカウントを役割別に用意した
  • まっさらな環境にゼロから構築できる手順が通る(マイグレーションの乖離チェック)

とくに4つ目は、この記事でいちばん伝えたい項目です。安全装置は「ある」ことではなく「働いている」ことに意味があります。

デモ公開についてよくある質問

Q. デモ環境はBasic認証で保護するのとどちらがいい?
見せる相手が特定のお客様だけなら、Basic認証(閲覧用の共通パスワード)をかける方が手軽です。ただし「URLを貼るだけで誰でも試せる」ことが営業資産になる製品なら、本記事のような読み取り専用設計で全公開する価値があります。

Q. 自社サービスのデモ環境を作ってもらうことはできる?
できます。今回のDEMO_MODE・自動リセット・サンプルデータ生成の仕組みは、業務システム全般に流用できる設計です。「営業のたびに画面キャプチャを貼っている」「触ってもらえれば良さが伝わるのに」という製品をお持ちでしたら、お気軽にご相談ください

Q. デモを触って気に入ったら、そのまま使える?
YORISOIは月額3,980円(税別・初期リリース記念価格)でご提供しています。デモと同じ画面を、御社専用のデータ領域でそのままお使いいただけます。Excelからのデータ移行もご協力します。詳しくは紹介ページをご覧ください。

シリーズのまとめ

全4回、お読みいただきありがとうございました。Excelのシフト表に悩む現場の相談から始まり、データベース設計、カレンダーUIの自作、そして今回のデモ公開まで——ひとつの業務システムが形になるまでの過程を、実際のコードと失敗込みで記録してきました。

連載は今回で完結ですが、YORISOIの開発は続きます。まずは公開デモを、ぜひ触ってみてください。

このシステムについて

この連載で作った訪問看護シフト管理システムは「YORISOI」として提供しています。公開デモ・機能の詳細はこちらから。

公開デモを触ってみる →YORISOIの紹介ページを見る →

お仕事のご依頼について

普段は中小企業様向けのWeb制作・システム開発を中心に活動しています。「自社サービスのデモ環境を作りたい」「うちの業務もこういうシステムにしたい」など、お気軽にご相談ください。

開発の相談をしてみる →

Profile

anTech / アンテク

地域の中小企業様向けにシステム開発・Web制作を行うフリーランスエンジニア。「ちょうどいい」技術で、御社のビジネスをサポートします。

埼玉県川越市拠点 — 川越市・日高市・狭山市・鶴ヶ島市を中心に埼玉県全域対応