#author("2025-04-28T20:51:05+09:00","default:Shigure","Shigure") ~> [[Category/Security]] #author("2025-04-30T01:22:50+09:00","default:Shigure","Shigure") ~> [[Category/*CreatedByAI]] * セキュアなフォーム認証の実装(PHP + JS) [#secure_login] ** ▼ 前提条件 [#j69a2b87] - サーバーサイドはPHP - フロントエンドはHTML + JavaScript(任意) - データベース(例:MySQL)にユーザー情報が保存されている - パスワードは `password_hash()` でハッシュ化済み ** ▼ バージョン1:PHPのみのセキュア認証フォーム [#x352e0c5] *** login.php(ログイン画面) [#vd6b34cd] #code(php){{ <?php session_start(); $csrf_token = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = $csrf_token; ?> <form method="post" action="auth.php"> <label for="username">ユーザー名:</label> <input type="text" name="username" required><br> <label for="password">パスワード:</label> <input type="password" name="password" required><br> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>"> <button type="submit">ログイン</button> </form> #}} *** auth.php(認証処理) [#ycd2835a] #code(php){{ <?php session_start(); // CSRFトークン確認 if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { die('CSRFトークンが無効です'); } $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; // DB接続(例) $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass'); // SQLインジェクション対策 $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { session_regenerate_id(true); $_SESSION['loggedin'] = true; $_SESSION['username'] = $user['username']; echo "ログイン成功!"; } else { echo "ユーザー名またはパスワードが間違っています"; } #}} ** ▼ バージョン2:PHP + JavaScriptでのフォーム(UX改善) [#u5d7255a] *** login.php(クライアント側入力チェックあり) [#q1be9d5b] #code(php){{ <?php session_start(); $csrf_token = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = $csrf_token; ?> <form id="loginForm" method="post" action="auth.php"> <label for="username">ユーザー名:</label> <input type="text" id="username" name="username" required><br> <label for="password">パスワード:</label> <input type="password" id="password" name="password" required><br> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>"> <button type="submit">ログイン</button> </form> <script> document.getElementById('loginForm').addEventListener('submit', function(e) { const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value.trim(); if (username === '' || password === '') { e.preventDefault(); alert("ユーザー名とパスワードは必須です。"); } }); </script> #}} *** auth.php は同じ内容を再利用可能 [#we53bb47] ** ▼ セキュリティ対策チェックリスト [#vdd2ec1a] - [x] `password_hash()` / `password_verify()` でパスワード保護 - [x] `session_regenerate_id(true);` でセッション固定攻撃対策 - [x] CSRFトークンを hidden に埋め込む - [x] SQLインジェクション対策(PDO + プレースホルダ) - [x] 出力時は `htmlspecialchars()` でXSS対策 ** ▼ 補足:次に実装する機能例 [#o861f8b2] - [[ユーザー登録(signup.php)]] - [[ログアウト処理(logout.php)]] - [[ログイン済み専用ページ(dashboard.php)]] - [[2段階認証(TOTPなど)]] #br #br #br #br #br #br #br #hr #br #br #br #br #br #br #br * データベースを使わないセキュアなログイン認証 [#dbfree_login] ** ▼ 概要 [#j3i1c7ea] - ユーザー情報はPHPコード内に定義(配列) - パスワードは `password_hash()` でハッシュ化済み - データベース接続不要 - 小規模・個人用におすすめ ** ▼ login.php(ログインフォーム) [#x4kk19e2] #code(php){{ <?php session_start(); $csrf_token = bin2hex(random_bytes(32)); $_SESSION['csrf_token'] = $csrf_token; ?> <form method="post" action="auth.php"> <label for="username">ユーザー名:</label> <input type="text" name="username" required><br> <label for="password">パスワード:</label> <input type="password" name="password" required><br> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>"> <button type="submit">ログイン</button> </form> #}} ** ▼ auth.php(認証処理) [#z3sa2k21] #code(php){{ <?php session_start(); // CSRFチェック if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { die('CSRFトークンが無効です'); } $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; // 事前登録されたユーザー(パスワードは password_hash() の出力) $users = [ 'admin' => '$2y$10$8s4zM5k7BnEl9P57lY2qDO1LVJ3AsGHDDe8zUlqD9uJXsM5Xx9RUW', // password: mypass123 'user' => '$2y$10$ZK1K9yUO7cV9GrGc9eZXzO.xFNZ4kg9w6FxJYVbmuMyO0r3aPpGQK' // password: secret456 ]; // ユーザー存在とパスワード検証 if (isset($users[$username]) && password_verify($password, $users[$username])) { session_regenerate_id(true); $_SESSION['loggedin'] = true; $_SESSION['username'] = $username; echo "ログイン成功!"; } else { echo "ユーザー名またはパスワードが間違っています"; } #}} ** ▼ パスワードハッシュの生成方法(事前に実行) [#genpass] 以下のようなスクリプトで一度ハッシュを生成して、auth.php にコピペします。 #code(php){{ <?php echo password_hash('mypass123', PASSWORD_DEFAULT); #}} ** ▼ 注意点・セキュリティ強化策 [#tips] - [x] パスワードは必ず `password_hash()` でハッシュ化して保存 - [x] CSRFトークンを使用 - [x] `session_regenerate_id(true);` をログイン時に使用 - [x] 配列にユーザー名とハッシュを直接記述するだけなので、他のファイルからは読み込まれないよう注意 ** ▼ この方式が向いているケース [#usecases] - 個人利用サイト - 小規模管理画面(ブログ管理など) - ユーザー追加は手動でも問題ない環境