🔒

機密情報を守る:React + Go で作る「完全クライアント完結型」QRコード生成器

に公開

社内の Wi-Fi 情報や限定公開 URL を共有する際、既存の SaaS 型 QR 生成サービスを利用すると「入力値が第三者の環境に送信される」という構造的なセキュリティリスクが伴います。

この課題を解決するため、「ブラウザ内で生成が完結し、サーバー側には一切データを送らない」構成の QR コード生成ツールを開発しました。本記事では、そのアーキテクチャと実装のポイントを紹介します。

GitHub リポジトリ: ttokunaga-jp/QR_Code_Generator

なぜこのリポジトリを作ったのか

もっとも大きな動機は 情報漏洩リスクの排除 です。

  1. プライバシーの担保: Wi-Fi のパスワードや機密性の高い URL を外部サーバーに送信したくない。
  2. 静的アセットの配布: 実行時にサーバーサイドのロジックを必要とせず、ビルド済みの静的ファイルのみを配信する構成にしたい。
  3. セルフホストの容易さ: Docker 一つでどこでも安全に動かせるようにしたい。

最初に決めた開発方針

開発にあたり、以下の 4 つのポリシーを策定しました。

  • ミニマル構成: frontend/backend/ のシンプルなディレクトリ構成を維持。
  • 静的配信のみ: 実行時にサーバーへ入力を送らない。Go サーバーは単なる配信基盤として機能させる。
  • 再現性の保証: Makefile と Docker マルチステージビルドを活用し、環境に依存せず同じ成果物を得られるようにする。
  • 運用の簡素化: docker-compose.yml でプロキシ設定などを切り替え可能にし、CI/CD への組み込みを容易にする。

アーキテクチャの概要

システム全体の流れは以下の通りです。

┌────────────┐   ビルド   ┌───────────────┐
│ React + Vite │─────────▶│ /frontend/build │
└────────────┘            └───────────────┘
       ▲                                 │
       │ i18next, Tailwind               │ STATIC_DIR=/app/build
       │                                 ▼
┌────────────┐   go build  ┌───────────────┐
│  Go server  │───────────▶│ /server (bin) │
└────────────┘            └───────────────┘

                             Docker multi-stage

                               alpine runtime

フロントエンド

React + TypeScript + Vite を採用。
QRCode.toCanvas を用いて、ブラウザの DOM 内で直接 QR コードを描画します。i18next による日英切り替えにも対応しています。

バックエンド

Go 1.22 による軽量な静的ファイルサーバーです。
/healthz エンドポイントを備え、コンテナの死活監視に対応。入力値を受け取る API は一切持ちません。

インフラ

Node → Go → Alpine の 3 段マルチステージ構成の Dockerfile を作成。最終的なイメージサイズを最小化しています。

フロントエンド実装の工夫

ブラウザ内での QR 生成

入力イベントをトリガーに、ライブラリを用いて即時描画を行っています。

frontend/src/components/QRGeneratorApp.tsx
const wifiString = `WIFI:T:${encryption};S:${ssid};P:${password};H:false;;`;
await QRCode.toCanvas(canvasRef.current, wifiString, {
  width: qrSize,
  margin: 2,
  color: { dark: '#000000', light: '#FFFFFF' }
});
  • バリデーション: Wi-Fi モードでは SSID 必須チェック、URL モードでは new URL() による形式検証を実施し、誤った QR 生成を抑制。
  • ダウンロード機能: Canvas の toDataURL を活用し、PNG 画像として保存可能。追加ライブラリなしでブラウザ標準 API のみで完結させました。

バックエンド実装の工夫

Go で書かれたサーバーは、配信時の安定性を高めるための実装を施しています。

  1. SPA ルーティング: フロントエンド側でのルーティング拡張に備え、存在しないパスへのリクエストは index.html を返すようにハンドリング。
  2. キャッシュ制御: index.html の更新が即座に反映されるよう、Cache-Control: no-store を明示。
  3. 環境変数による柔軟性: 配信ディレクトリ (STATIC_DIR) やポート番号を環境変数で上書き可能にしています。

ビルドとデプロイの流れ

Makefile によるローカル再現

開発者が迷わないよう、コマンドを共通化しています。

make build         # npm ci → npm run build → go build
make frontend-build
make backend-build

Docker マルチステージビルド

Dockerfile 内で以下のプロセスを完結させています。

  1. frontend-builder: Node 環境で Vite ビルドを実行。
  2. server-builder: Go 環境で配信サーバーのバイナリを作成。
  3. runtime: 最小限の Alpine イメージに上記 2 つの成果物のみをコピー。

これにより、攻撃面(Attack Surface)を最小限に抑えた実行環境が構築されます。

セキュリティ観点でのまとめ

  • データの局所性: 入力値はブラウザから外に出ません。
  • 透明性: 「サーバーへ送信しない」ことを UI 上で明示。
  • 堅牢なイメージ: ランタイム層にビルドツールを含めないことで、脆弱性リスクを低減。

これからの拡張アイデア

今後、さらに利便性を高めるために以下の機能を検討しています。

  • PWA 化: Service Worker を導入し、完全オフライン環境での QR 生成を実現。
  • テンプレート保存: よく使う設定を LocalStorage に保存。
  • カスタマイズ性: ロゴの合成やカラー変更機能の追加。

「入力値を預からない」というシンプルな設計ですが、社内ツールとしては非常に強力な安心感を提供できます。似たような課題感をお持ちの方は、ぜひリポジトリを参考にしてみてください。

実装の検証状況(2025-12-01)

ローカル環境および Docker コンテナ上にて、Wi-Fi モード・URL モード双方の生成・ダウンロード機能が正常に動作することを確認済みです。

GitHubで編集を提案

Discussion