LoginSignup
2

posted at

updated at

PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ユーザー登録編】

PHP + MySQLフルスクラッチでのユーザー認証機能の実装方法を解説します。今回は【ユーザー登録編】です。
開発環境、ファイル構成、ER図はこちらからご確認ください。

要件定義

  • 入力フォームはユーザー名、パスワード、パスワード(確認用)の3つ
  • すでに使用されているユーザー名は登録できない
  • ユーザー名は半角英数字20文字以内
  • パスワードは半角英数字8文字以上で英大文字、英子文字、数字を最低1個以上含む
  • ユーザー登録完了後、自動でログイン処理を行いインデックスページへリダイレクト

ユーザー登録画面 (sign_up.php)

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対策のサニタイズ用ファイルを呼び出します。
その後、セッションを利用してログイン/ログアウトの状態を判定します。すでにログイン状態ならば、インデックスページへリダイレクトさせます。
ログイン編で行った処理とまったく同じです。

sign_up.php
require_once('common/db_connect.php');
require_once('common/sanitize.php');

// セッションの開始
session_start();

// ログイン済みの場合、マイページへリダイレクト
if (isset($_SESSION['id'])) {
    header('Location: index.php');
    exit;
}

ユーザー名の重複チェック

sign_up.php
// フォームから値が入力された場合
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文を使って、ユーザーから入力された名前に一致するレコード数を取り出します。$result0ならそのユーザー名は未使用、1ならばすでに使用されているということになります。

バリデーション

sign_up.php
    /* バリデーション */
    // ユーザー名の重複
    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にエラーメッセージを格納し、ビューにエラーメッセージを表示させます。

登録処理

sign_up.php
    // バリデーションクリア(エラーメッセージなし)の場合
    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文を使ってユーザー情報を新規レコードとして登録しましょう。
これで登録処理は完了です。

ログイン処理

sign_up.php
        // 登録に引き続き、ログイン処理を行う
        $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;
    }
}

登録に引き続き、そのままログイン処理に入ります。ロジックは【ログイン・ログアウト編】で解説した内容と同じですので、ここでの解説は割愛します。

まとめ

ここまでの作業、おつかれさまでした。
次回はマイページとパスワード変更機能の実装をしていきます。

シリーズまとめ

  1. PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ログイン・ログアウト編】
  2. PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ユーザー登録編】 (本稿)
  3. PHP + MySQLフルスクラッチでユーザー認証機能を実装する【マイページ・パスワード変更機能編】
  4. PHP + MySQLフルスクラッチでユーザー認証機能を実装する【ユーザー登録削除編】

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2