[PHP] 簡易ログインアカウント登録機能を実装してみた

## はじめに
前回の記事でログイン機能を実装しました。
**https://qiita.com/N46_myHearter/items/ae4f776790c4c4666a66**

この記事では、ログインする際に使用するアカウントをデータベースに登録する機能を実装してみたいと思います。

###**基本的な流れ**
1. 登録するユーザ情報(ID、パスワード、ユーザ名)を入力します。
2. 入力値チェックを行う。
3. ユーザ情報をDBに登録する。

簡易なので...笑笑

## データベース
今回は、MySQLを使用しています。
データ項目は、**ID**、**パスワード**、**ユーザ名**の3つのみで構成しています。
プライマリーキーにIDを指定しています。
パスワードは、ハッシュ化で暗号化。(調べた時にハッシュ化だと簡単に出来たから笑笑)

## 該当ソースコード
### 1. register.php

アカウントを登録するための入力画面です。
入力項目は、ID、名前、パスワード(確認のため2回入力)です。
入力項目すべて入力されているか、2回入力したパスワードが一致しているか、
パスワードの強度は大丈夫か(強度が弱いと警告)を確認して、アカウント登録確認画面に遷移します。

パスワード強度チェックは以下のHPにあるオープンソースであるpasswordchecker.jsを利用しています。
出典 https://www.websec-room.com/passswordchecker

```php:アカウント登録画面(register.php)
<?php
  session_start();
  header("Content-type: text/html; charset=utf-8");
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <link rel="stylesheet" type="text/css" href="style1.css"> 
  <meta charset="UTF-8">
  <script src="passwordchecker.js" type="text/javascript"></script>
  <script src="common.js" type="text/javascript"></script>
  <script type="text/javascript">
  
  /*
  * 登録前チェック  
  */
  function conrimMessage() {
    var id = document.getElementById("id").value;
    var name = document.getElementById("name").value;
    var pass = document.getElementById("password").value;
    var conf = document.getElementById("confirm_password").value;

    //必須チェック
    if((id == "") || (name == "") || (pass == "") || (conf == "")) {
      alert("必須項目が入力されていません。");
      return false;
    }

    //パスワードチェック
    if (pass != conf) {
      alert("パスワードが一致していません。");
      return false;
    }

    if (passwordLevel < 3) {
      return confirm("パスワード強度が弱いですがよいですか？");
    }

    return true;
    }

  </script>
</head>

<div class="form-wrapper">
  <h1>アカウント登録</h1>
  
  <?php
    if (isset($_SESSION["error_status"])) {
      if ($_SESSION["error_status"] == 1) {
        echo "<h2 style='color:red;'>入力内容に誤りがあります。</h2>";
      }
      if ($_SESSION["error_status"] == 2) {
        echo "<h2 style='color:red;'>IDは既に登録されています。</h2>";
      }
      if ($_SESSION["error_status"] == 3) {
        echo "<h2 style='color:red;'>タイムアウトか不正な URL です。</h2>";
      }
      if ($_SESSION["error_status"] == 4) {
        echo "<h2 style='color:red;'>登録に失敗しました。</h2>";
      }
      if ($_SESSION["error_status"] == 5) {
        echo "<h2 style='color:red;'>パスワードは既に登録されています。</h2>";
      }
    }
  ?>

  <form action="register_check.php" method="post" onsubmit="return conrimMessage();">
  <div class="form-item">
    <label for="ID"></label>
    <input type="text" name="id" id="id" required="required" placeholder="ID">
  </div>
  <div class="form-item">
    <label for="NAME"></label>
    <input type="text" name="name" id="name" required="required" placeholder="名前">
  </div>
  <div class="form-item">
    <label for="password"></label>
    <input type="password" name="password" required="required" placeholder="パスワード">
  </div>
  <div class="form-item">
    <label for="password"></label>
    <input type="password" name="confirm_password" id="confirm_password" required="required" placeholder="パスワード(確認)">
  </div>

  <div class="button-panel">
    <input type="submit" class="button" title="登録" value="登録">
  </div>
  </form>

  <div class="form-footer">
    <p><a href="/login.php">戻る</a></p>
  </div>
</div>
</html>
```

### 2. register_check.php
本当にアカウント登録していいのか再度確認する画面です。
また、入力したアカウント情報がDBに既に存在していないかをチェックします。
既に登録されていた場合はエラーを返します。

```php:アカウント登録確認画面(register_check.php)
<?php
  require_once("function.php");
  session_start();
  header("Content-type: text/html; charset=utf-8");  
  
  $_SESSION['token']  = get_csrf_token();

  $id = $_POST['id'];
  $password = $_POST['password'];
  $name = $_POST['name'];
  $confirm_password = $_POST['confirm_password'];

  if ($password != $confirm_password) {
    //パスワード不一致 
    $_SESSION["error_status"] = 1;
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: register.php");
    exit();
  }  

  //IDチェック

  //DB接続
  try {
    /*dbname: test
      host: localhost (DBをローカル環境に置いている)
      username: root
      password: '' (未設定)
    */
    $pdo = new PDO('mysql:dbname=test;host=localhost;charset=utf8mb4', 'root', '',
      [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
      ]
      );

    $stmt = $pdo->query('SELECT id, password FROM USER');

    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
      $db_id = $row["id"];
      $db_password = $row["password"];

    //既にIDが登録されていた
    if ($db_id == $id) {
      $_SESSION["error_status"] = 2;
      header("HTTP/1.1 301 Moved Permanently");
      header("Location: register.php");
      exit();
    } 

    //エラー情報リセット
    $_SESSION["error_status"] = 0;
    }
    } catch (PDOException $e) {
      exit('データベース接続失敗。'.$e->getMessage());
    }

?>

<!DOCTYPE html>
<head>
  <link rel="stylesheet" type="text/css" href="style1.css">
  <meta charset="utf-8">
</head>

<html lang="ja">
<div class="form-wrapper">
  <h1>登録確認</h1>
  <h2>アカウント登録しますか？</h2>
  <form action="register_submit.php" method="post">
    <table border="0">
    
      <input type="hidden" name="id" value="<?php echo htmlspecialchars($id  , ENT_QUOTES, "UTF-8") ?>">
      <input type="hidden" name="name" value="<?php echo htmlspecialchars($name  , ENT_QUOTES, "UTF-8") ?>">
      <input type="hidden" name="password" value="<?php echo htmlspecialchars($password  , ENT_QUOTES, "UTF-8") ?>">
      <input type="hidden" name="token" value="<?php echo htmlspecialchars($_SESSION['token']  , ENT_QUOTES, "UTF-8") ?>">

      <div class="button-panel">
        <input type="submit" class="button" title="登録" value="登録">
      </div>
    </form>

    <div class="form-footer">
        <p><a href="/register.php">戻る</a></p>
    </div>
</div>
</html>
```

### 3. register_submit.php
実際にアカウント登録をして、登録完了を知らせる画面です。
DBに入力したユーザ情報を格納します。
その時、パスワードはハッシュ化で暗号化します。

```php:アカウント登録完了画面(register_submit.php)
<?php
  require_once("function.php");
  session_start();
  header("Content-type: text/html; charset=utf-8");

  //CSRF チェック
  if ($_SESSION['token'] != $_POST['token']) {
      $_SESSION = array();
      session_destroy();
      session_start();

    $_SESSION["error_status"] = 2;
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: login.php");
    exit();
  } 

  //エラー情報のリセット
  $_SESSION["error_status"] = 0;

  $id = $_POST['id'];
  $password = $_POST['password'];
  $name = $_POST['name'];

  // ハッシュを作る
  $hash = password_hash($password, PASSWORD_BCRYPT);

  //DB接続
  try {
    /*dbname: test
      host: localhost (DBをローカル環境に置いている)
      username: root
      password: '' (未設定)
    */
    $pdo = new PDO('mysql:dbname=test;host=localhost;charset=utf8mb4', 'root', '', 
    [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
    );

    //パラメーターの型を指定
    $stmt = $pdo->prepare('INSERT INTO USER (ID, NAME, PASSWORD) VALUES (:id, :name, :password)'); 
    $stmt->bindParam(':id', $id, PDO::PARAM_STR);
    $stmt->bindParam(':name', $name, PDO::PARAM_STR);
    $stmt->bindParam(':password', $hash, PDO::PARAM_STR);

    $stmt->execute();
  } catch (PDOException $e) {
    exit('データベース接続失敗。'.$e->getMessage());
  }
  
?>

<!DOCTYPE html>
<html lang="ja">
<head>
  <link rel="stylesheet" type="text/css" href="style1.css">
  <meta charset="utf-8">
</head>

<<div class="form-wrapper">
  <h1>アカウント登録完了</h1>
  アカウント登録が終了しました。<br>
  ログイン画面からログインしてください。<br><br>

	<div class="form-footer">
    <p><a href="/login.php">ログイン画面に戻る</a></p>
  </div>
</div>
</html>
```

### 4. function.php
以下の前回の記事に掲載済み。
https://qiita.com/N46_myHearter/items/ae4f776790c4c4666a66

## 環境
windows10 64bit
実行環境: xampp (mysql、php、apache等をまとめてインストールでき、インストールも簡単なので)

## おわりに
抜けている箇所がありましたら、コメント欄で教えていただくとありがたいです。