PHP + MySQLフルスクラッチでのユーザー認証機能の実装方法を解説します。今回は【ユーザー登録編】です。
開発環境、ファイル構成、ER図はこちらからご確認ください。
要件定義
- 入力フォームはユーザー名、パスワード、パスワード(確認用)の3つ
- すでに使用されているユーザー名は登録できない
- ユーザー名は半角英数字20文字以内
- パスワードは半角英数字8文字以上で英大文字、英子文字、数字を最低1個以上含む
- ユーザー登録完了後、自動でログイン処理を行いインデックスページへリダイレクト
ユーザー登録画面 (sign_up.php)
<!-- ロジック
================================================================================================ -->
<?php
require_once('common/db_connect.php');
require_once('common/sanitize.php');
// セッションの開始
session_start();
// ログイン済みの場合、マイページへリダイレクト
if (isset($_SESSION['id'])) {
header('Location: index.php');
exit;
}
// フォームから値が入力された場合
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// フォームの入力値を代入
$name = $_POST['name'];
$pass = $_POST['pass'];
$pass_check = $_POST['pass_check'];
// 入力されたユーザー名に一致するレコード数を取得
$sql = 'SELECT COUNT(*) FROM users WHERE name = :name';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
/* バリデーション */
// ユーザー名の重複
if ($result['COUNT(*)'] == 1) {
$errors['name'] = '※このユーザー名は既に使用されています';
}
// ユーザー名の形式
if (!preg_match('/^[a-zA-Z0-9]{1,20}$/', $name)) {
$errors['name'] = '※ユーザー名は半角英数字20文字以内で入力してください';
}
// パスワードの形式
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]{8,}$/', $pass)) {
$errors['pass'] = '※パスワードは半角英数字8文字以上で英大文字、英子文字、数字を最低1個以上含む必要があります';
}
// 確認用パスワードとの一致
if ($pass !== $pass_check) {
$errors['pass_check'] = '※確認用パスワードが一致しません';
}
// バリデーションクリア(エラーメッセージなし)の場合
if (empty($errors)) {
// パスワードの暗号化
$hash_pass = password_hash($pass, PASSWORD_DEFAULT);
// ユーザー登録処理
$sql = 'INSERT INTO users SET name = :name, password = :pass';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->bindValue(':pass', $hash_pass, PDO::PARAM_STR);
$stmt->execute();
// 登録に引き続き、ログイン処理を行う
$sql = 'SELECT * FROM users WHERE name = :name and password = :pass';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->bindValue(':pass', $hash_pass, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
// セッションIDを新しく生成(セッションハイジャック対策)
session_regenerate_id(true);
// ログイン処理
$_SESSION['id'] = $result['id'];
$_SESSION['name'] = $result['name'];
// インデックスページへリダイレクト
header('Location: index.php');
exit;
}
}
?>
<!-- ビュー
================================================================================================ -->
<!-- header 読み込み -->
<?php require_once('common/header.php') ?>
<body>
<main>
<h1>ユーザー登録</h1>
<p>任意のユーザー名とパスワードを登録してください</p>
<!-- 入力フォーム -->
<form method="post">
<div>
<label>ユーザー名</label>
<input type="text" name="name" id="name" required>
<p><?= isset($errors['name']) ? escape($errors['name']) : '' ?></p>
</div>
<div>
<label for="pass">パスワード</label>
<input type="password" name="pass" id="pass" required>
<p><?= isset($errors['pass']) ? escape($errors['pass']) : '' ?></p>
</div>
<div>
<label for="pass_check">パスワード(確認用)</label>
<input type="password" name="pass_check" id="pass_check" required>
<p><?= isset($errors['pass_check']) ? escape($errors['pass_check']) : '' ?></p>
</div>
<div>
<button type="submit">登録</button>
</div>
</form>
</main>
</body>
</html>
DB接続~ログイン状態の判定
前回作成した共通化コンポーネントから、DB接続とXXS対策のサニタイズ用ファイルを呼び出します。
その後、セッションを利用してログイン/ログアウトの状態を判定します。すでにログイン状態ならば、インデックスページへリダイレクトさせます。
ログイン編で行った処理とまったく同じです。
require_once('common/db_connect.php');
require_once('common/sanitize.php');
// セッションの開始
session_start();
// ログイン済みの場合、マイページへリダイレクト
if (isset($_SESSION['id'])) {
header('Location: index.php');
exit;
}
ユーザー名の重複チェック
// フォームから値が入力された場合
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// フォームの入力値を代入
$name = $_POST['name'];
$pass = $_POST['pass'];
$pass_check = $_POST['pass_check'];
// 入力されたユーザー名に一致するレコード数を取得
$sql = 'SELECT COUNT(*) FROM users WHERE name = :name';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
フォームから入力されたユーザー名がすでに使用されていないかチェックします。
SELECT COUNT(*) FROM users WHERE name = :name';
というSQL文を使って、ユーザーから入力された名前に一致するレコード数を取り出します。$result
が0
ならそのユーザー名は未使用、1
ならばすでに使用されているということになります。
バリデーション
/* バリデーション */
// ユーザー名の重複
if ($result['COUNT(*)'] == 1) {
$errors['name'] = '※このユーザー名は既に使用されています';
}
// ユーザー名の形式
if (!preg_match('/^[a-zA-Z0-9]{1,20}$/', $name)) {
$errors['name'] = '※ユーザー名は半角英数字20文字以内で入力してください';
}
// パスワードの形式
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]{8,}$/', $pass)) {
$errors['pass'] = '※パスワードは半角英数字8文字以上で英大文字、英子文字、数字を最低1個以上含む必要があります';
}
// 確認用パスワードとの一致
if ($pass !== $pass_check) {
$errors['pass_check'] = '※確認用パスワードが一致しません';
}
?>
<!-- ビュー
================================================================================================ -->
<!-- 入力フォーム -->
<form method="post">
<div>
<label>ユーザー名</label>
<input type="text" name="name" id="name" required>
<p><?= isset($errors['name']) ? escape($errors['name']) : '' ?></p>
</div>
<div>
<label for="pass">パスワード</label>
<input type="password" name="pass" id="pass" required>
<p><?= isset($errors['pass']) ? escape($errors['pass']) : '' ?></p>
</div>
<div>
<label for="pass_check">パスワード(確認用)</label>
<input type="password" name="pass_check" id="pass_check" required>
<p><?= isset($errors['pass_check']) ? escape($errors['pass_check']) : '' ?></p>
</div>
<div>
<button type="submit">登録</button>
</div>
</form>
各バリデーションを用意します。
バリデーションに引っかかった場合、配列$errors
にエラーメッセージを格納し、ビューにエラーメッセージを表示させます。
登録処理
// バリデーションクリア(エラーメッセージなし)の場合
if (empty($errors)) {
// パスワードの暗号化
$hash_pass = password_hash($pass, PASSWORD_DEFAULT);
// ユーザー登録処理
$sql = 'INSERT INTO users SET name = :name, password = :pass';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->bindValue(':pass', $hash_pass, PDO::PARAM_STR);
$stmt->execute();
バリデーションをすべてクリアしたら、ユーザー登録処理を行います。
バリデーションをクリアしたかどうかの判定は、配列$errors
が空か(エラーメッセージが格納されていないか)という条件で行っています。
登録処理に進む前に、 パスワードを暗号化 しておく必要があります。もし暗号化せずにパスワードをそのままデータベースへ保管すると、システム管理者が利用者のパスワードを確認できてしまう、悪意のある第三者がデータベースに侵入した場合、簡単にパスワードを盗用できてしまう、といった重大なセキュリティ脆弱性が発生します。
PHPではpassword_hash
関数を使用することで、簡単にパスワードの暗号化が行えます。
パスワードの暗号化が完了したら、INSERT
文を使ってユーザー情報を新規レコードとして登録しましょう。
これで登録処理は完了です。
ログイン処理
// 登録に引き続き、ログイン処理を行う
$sql = 'SELECT * FROM users WHERE name = :name and password = :pass';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->bindValue(':pass', $hash_pass, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
// セッションIDを新しく生成(セッションハイジャック対策)
session_regenerate_id(true);
// ログイン処理
$_SESSION['id'] = $result['id'];
$_SESSION['name'] = $result['name'];
// インデックスページへリダイレクト
header('Location: index.php');
exit;
}
}
登録に引き続き、そのままログイン処理に入ります。ロジックは【ログイン・ログアウト編】で解説した内容と同じですので、ここでの解説は割愛します。
まとめ
ここまでの作業、おつかれさまでした。
次回はマイページとパスワード変更機能の実装をしていきます。
シリーズまとめ
- PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ログイン・ログアウト編】
- PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ユーザー登録編】 (本稿)
- PHP + MySQLフルスクラッチでユーザー認証機能を実装する【マイページ・パスワード変更機能編】
- PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ユーザー登録削除編】
参考