見出し画像

【PHP】メール認証を利用した会員登録機能の作成方法

今回はPHPにてメール認証を利用した会員登録機能の作成方法を記載します。

概要

機能概要としてはこんな感じです。

要件.001

※ごめんなさい、ちょっと細かくて見辛いので画像クリックして拡大してみてください...!!

それぞれ詳しく説明しますと、

1.会員仮登録画面
- メールアドレスを登録します。

2.会員仮登録完了画面
- メールアドレスを登録したというお知らせの画面です。
- この時にpre_userテーブルにデータを登録します。

3.メール送信
- 登録されたメールアドレスに登録フォームのURLを記載したメールを送信します。

4.会員本登録画面
- 会員本登録のフォームを表示します。 

5.会員本登録確認画面
- フォームの登録内容の確認画面です。

6.登録完了
- 登録完了を報告する画面です。

7.メール送信
- 登録されたメールアドレスに、会員登録完了のメールを送信します。


DBの作成

まずはDBを作成します。

今回は下記2つのテーブルを作成します。

pre_user(仮登録用のテーブル)
user(本登録用のテーブル)

データ定義は下記で設定します。

スクリーンショット 2019-10-10 9.43.37

下記クエリを実行し、テーブルの作成をお願いします。

- pre_userテーブルの作成

CREATE TABLE pre_user (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
urltoken VARCHAR(128) NOT NULL,
mail VARCHAR(50) NOT NULL,
date DATETIME NOT NULL,
flag TINYINT(1) NOT NULL DEFAULT 0
)ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

- userテーブルの作成

CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
password VARCHAR(128) NOT NULL,
mail VARCHAR(128) NOT NULL,
status INT(1) NOT NULL DEFAULT 2,
created_at DATETIME,
updated_at DATETIME
)ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

テーブル作成されました。

スクリーンショット 2019-10-11 16.29.49


実装1:仮会員登録

まずは下記の仮登録部分から実装します。

スクリーンショット 2019-10-11 16.31.30

signup_mail.phpの作成

まずは「signup_mail.php」というファイルを作成します。下記記載します。

<?php
session_start();
//クロスサイトリクエストフォージェリ(CSRF)対策
$_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
$token = $_SESSION['token'];
//クリックジャッキング対策
header('X-FRAME-OPTIONS: SAMEORIGIN');
//DB情報
$user = 'test';//データベースユーザ名
$password = 'test1234';//データベースパスワード
$dbName = "sample";//データベース名
$host = "sp_db_1";//ホスト
//エラーメッセージの初期化
$errors = array();
//DB接続
$dsn = "mysql:host={$host};dbname={$dbName};charser=utf8";
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//送信ボタンクリックした後の処理
if (isset($_POST['submit'])) {
   //メールアドレス空欄の場合
   if (empty($_POST['mail'])) {
       $errors['mail'] = 'メールアドレスが未入力です。';
   }else{
       //POSTされたデータを変数に入れる
       $mail = isset($_POST['mail']) ? $_POST['mail'] : NULL;
   
       //メールアドレス構文チェック
       if(!preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $mail)){
			$errors['mail_check'] = "メールアドレスの形式が正しくありません。";
       }
       //DB確認        
       $sql = "SELECT id FROM user WHERE mail=:mail";
       $stm = $pdo->prepare($sql);
       $stm->bindValue(':mail', $mail, PDO::PARAM_STR);
       
       $stm->execute();
       $result = $stm->fetch(PDO::FETCH_ASSOC);
       //user テーブルに同じメールアドレスがある場合、エラー表示
       if(isset($result["id"])){
			$errors['user_check'] = "このメールアドレスはすでに利用されております。";
       }
       
   }
   //エラーがない場合、pre_userテーブルにインサート
   if (count($errors) === 0){
       $urltoken = hash('sha256',uniqid(rand(),1));
       $url = "http://localhost:8080/signup.php?urltoken=".$urltoken;
       //ここでデータベースに登録する
       try{
           //例外処理を投げる(スロー)ようにする
           $sql = "INSERT INTO pre_user (urltoken, mail, date, flag) VALUES (:urltoken, :mail, now(), '0')";
           $stm = $pdo->prepare($sql);
           $stm->bindValue(':urltoken', $urltoken, PDO::PARAM_STR);
           $stm->bindValue(':mail', $mail, PDO::PARAM_STR);
           $stm->execute();
           $pdo = null;
           $message = "メールをお送りしました。24時間以内にメールに記載されたURLからご登録下さい。";     
       }catch (PDOException $e){
           print('Error:'.$e->getMessage());
           die();
       }
       /*
       * メール送信処理
       * 登録されたメールアドレスへメールをお送りする。
       * 今回はメール送信はしないためコメント
       */
       /*  
   	$mailTo = $mail;
       $body = <<< EOM
       この度はご登録いただきありがとうございます。
       24時間以内に下記のURLからご登録下さい。
       {$url}
EOM;
       mb_language('ja');
       mb_internal_encoding('UTF-8');
   
       //Fromヘッダーを作成
       $header = 'From: ' . mb_encode_mimeheader($companyname). ' <' . $companymail. '>';
   
       if(mb_send_mail($mailTo, $registation_subject, $body, $header, '-f'. $companymail)){      
           //セッション変数を全て解除
           $_SESSION = array();
           //クッキーの削除
           if (isset($_COOKIE["PHPSESSID"])) {
               setcookie("PHPSESSID", '', time() - 1800, '/');
           }
           //セッションを破棄する
           session_destroy();
           $message = "メールをお送りしました。24時間以内にメールに記載されたURLからご登録下さい。";
       }
       */
   }
}
?>
<h1>仮会員登録画面</h1>
<?php if (isset($_POST['submit']) && count($errors) === 0): ?>
   <!-- 登録完了画面 -->
   <p><?=$message?></p>
   <p>↓TEST用(後ほど削除):このURLが記載されたメールが届きます。</p>
   <a href="<?=$url?>"><?=$url?></a>
<?php else: ?>
<!-- 登録画面 -->
   <?php if(count($errors) > 0): ?>
       <?php
       foreach($errors as $value){
           echo "<p class='error'>".$value."</p>";
       }
       ?>
   <?php endif; ?>
   <form action="<?php echo $_SERVER['SCRIPT_NAME'] ?>" method="post">
       <p>メールアドレス:<input type="text" name="mail" size="50" value="<?php if( !empty($_POST['mail']) ){ echo $_POST['mail']; } ?>"></p> 
       <input type="hidden" name="token" value="<?=$token?>">
       <input type="submit" name="submit" value="送信">
   </form>
<?php endif; ?>


「/signup_mail.php」にアクセスすると下記のような画面が表示されます。

スクリーンショット 2019-10-11 17.21.40

メール記入して「送信」をクリックすると、下記のような画面になります。

スクリーンショット 2019-10-12 4.26.42

同時にメールを送信する必要がありますが、テスト用のためメール送信のロジックはコメントしています。(74〜103行目)
今回は代わりにリンクを表示させています。

※まだリンク先のファイルを作っていないので、クリックしてもエラーになります。

DBを確認すると、「pre_user」テーブルにデータが格納されています。

スクリーンショット 2019-10-12 4.30.03

これで仮登録が完了しました。


実装2:本会員登録

それでは本会員登録を実装します。

スクリーンショット 2019-10-12 4.32.27

スクリーンショット 2019-10-12 4.32.33

signup.phpの作成

「signup.php」というファイルを作成します。下記記載します​。

<?php
session_start();
//クロスサイトリクエストフォージェリ(CSRF)対策
$_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
$token = $_SESSION['token'];
//クリックジャッキング対策
header('X-FRAME-OPTIONS: SAMEORIGIN');

//成功・エラーメッセージの初期化
$errors = array();

//DB情報
$user = 'test';//データベースユーザ名
$password = 'test1234';//データベースパスワード
$dbName = "sample";//データベース名
$host = "sp_db_1";//ホスト
//DB接続
$dsn = "mysql:host={$host};dbname={$dbName};charser=utf8";
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

if(empty($_GET)) {
	header("Location: registration_mail");
	exit();
}else{
	//GETデータを変数に入れる
	$urltoken = isset($_GET["urltoken"]) ? $_GET["urltoken"] : NULL;
	//メール入力判定
	if ($urltoken == ''){
		$errors['urltoken'] = "トークンがありません。";
	}else{
		try{
			// DB接続	
			//flagが0の未登録者 or 仮登録日から24時間以内
			$sql = "SELECT mail FROM pre_user WHERE urltoken=(:urltoken) AND flag =0 AND date > now() - interval 24 hour";
           $stm = $pdo->prepare($sql);
			$stm->bindValue(':urltoken', $urltoken, PDO::PARAM_STR);
			$stm->execute();
			
			//レコード件数取得
			$row_count = $stm->rowCount();
			
			//24時間以内に仮登録され、本登録されていないトークンの場合
			if( $row_count ==1){
				$mail_array = $stm->fetch();
				$mail = $mail_array["mail"];
				$_SESSION['mail'] = $mail;
			}else{
				$errors['urltoken_timeover'] = "このURLはご利用できません。有効期限が過ぎたかURLが間違えている可能性がございます。もう一度登録をやりなおして下さい。";
			}
			//データベース接続切断
			$stm = null;
		}catch (PDOException $e){
			print('Error:'.$e->getMessage());
			die();
		}
	}
}

/**
* 確認する(btn_confirm)押した後の処理
*/
if(isset($_POST['btn_confirm'])){
	if(empty($_POST)) {
		header("Location: registration_mail.php");
		exit();
	}else{
		//POSTされたデータを各変数に入れる
		$name = isset($_POST['name']) ? $_POST['name'] : NULL;
		$password = isset($_POST['password']) ? $_POST['password'] : NULL;
		
		//セッションに登録
		$_SESSION['name'] = $name;
		$_SESSION['password'] = $password;

		//アカウント入力判定
		//パスワード入力判定
		if ($password == ''):
			$errors['password'] = "パスワードが入力されていません。";
		else:
			$password_hide = str_repeat('*', strlen($password));
		endif;

		if ($name == ''):
			$errors['name'] = "氏名が入力されていません。";
		endif;
		
	}
	
}

/**
* page_3
* 登録(btn_submit)押した後の処理
*/
if(isset($_POST['btn_submit'])){
	//パスワードのハッシュ化
	$password_hash =  password_hash($_SESSION['password'], PASSWORD_DEFAULT);

	//ここでデータベースに登録する
	try{
		$sql = "INSERT INTO user (name,password,mail,status,created_at,updated_at) VALUES (:name,:password_hash,:mail,1,now(),now())";
       $stm = $pdo->prepare($sql);
		$stm->bindValue(':name', $_SESSION['name'], PDO::PARAM_STR);
		$stm->bindValue(':mail', $_SESSION['mail'], PDO::PARAM_STR);
		$stm->bindValue(':password_hash', $password_hash, PDO::PARAM_STR);
		$stm->execute();

		//pre_userのflagを1にする(トークンの無効化)
		$sql = "UPDATE pre_user SET flag=1 WHERE mail=:mail";
		$stm = $pdo->prepare($sql);
		//プレースホルダへ実際の値を設定する
		$stm->bindValue(':mail', $mail, PDO::PARAM_STR);
		$stm->execute();
						
		/*
		* 登録ユーザと管理者へ仮登録されたメール送信
       */
/* 
		$mailTo = $mail.','.$companymail;
       $body = <<< EOM
       この度はご登録いただきありがとうございます。
		本登録致しました。
EOM;
       mb_language('ja');
       mb_internal_encoding('UTF-8');
   
       //Fromヘッダーを作成
       $header = 'From: ' . mb_encode_mimeheader($companyname). ' <' . $companymail. '>';
   
       if(mb_send_mail($mailTo, $registation_mail_subject, $body, $header, '-f'. $companymail)){          
           $message['success'] = "会員登録しました";
       }else{
           $errors['mail_error'] = "メールの送信に失敗しました。";
		}	
*/
		//データベース接続切断
		$stm = null;

		//セッション変数を全て解除
		$_SESSION = array();
		//セッションクッキーの削除
		if (isset($_COOKIE["PHPSESSID"])) {
				setcookie("PHPSESSID", '', time() - 1800, '/');
		}
		//セッションを破棄する
		session_destroy();

	}catch (PDOException $e){
		//トランザクション取り消し(ロールバック)
		$pdo->rollBack();
		$errors['error'] = "もう一度やりなおして下さい。";
		print('Error:'.$e->getMessage());
	}
}

?>

<h1>会員登録画面</h1>

<!-- page_3 完了画面-->
<?php if(isset($_POST['btn_submit']) && count($errors) === 0): ?>
本登録されました。

<!-- page_2 確認画面-->
<?php elseif (isset($_POST['btn_confirm']) && count($errors) === 0): ?>
	<form action="<?php echo $_SERVER['SCRIPT_NAME'] ?>?urltoken=<?php print $urltoken; ?>" method="post">
		<p>メールアドレス:<?=htmlspecialchars($_SESSION['mail'], ENT_QUOTES)?></p>
		<p>パスワード:<?=$password_hide?></p>
		<p>氏名:<?=htmlspecialchars($name, ENT_QUOTES)?></p>
		
		<input type="submit" name="btn_back" value="戻る">
		<input type="hidden" name="token" value="<?=$_POST['token']?>">
		<input type="submit" name="btn_submit" value="登録する">
	</form>

<?php else: ?>
<!-- page_1 登録画面 -->
	<?php if(count($errors) > 0): ?>
       <?php
       foreach($errors as $value){
           echo "<p class='error'>".$value."</p>";
       }
       ?>
   <?php endif; ?>
		<?php if(!isset($errors['urltoken_timeover'])): ?>
			<form action="<?php echo $_SERVER['SCRIPT_NAME'] ?>?urltoken=<?php print $urltoken; ?>" method="post">
				<p>メールアドレス:<?=htmlspecialchars($mail, ENT_QUOTES, 'UTF-8')?></p>
				<p>パスワード:<input type="password" name="password"></p>
				<p>氏名:<input type="text" name="name" value="<?php if( !empty($_SESSION['name']) ){ echo $_SESSION['name']; } ?>"></p>
				<input type="hidden" name="token" value="<?=$token?>">
				<input type="submit" name="btn_confirm" value="確認する">
			</form>
		<?php endif ?>
<?php endif; ?>


先ほどのリンク(/signup.php?urltoken=*****)にクリックすると下記のような画面に遷移します。

スクリーンショット 2019-10-12 5.12.46

メールアドレスは「pre_user」のデータを自動で取得します。

「パスワード」と「氏名」を入力して「確認する」をクリックすると確認画面に遷移します。

スクリーンショット 2019-10-12 5.13.00

「戻る」をクリックで登録画面に遷移します。

スクリーンショット 2019-10-12 5.14.33

※パスワードはリセットされます。

「登録する」をクリックすると登録完了画面が表示されます。

スクリーンショット 2019-10-12 5.17.35

DBを確認すると、「user」テーブルにデータが格納されています。

スクリーンショット 2019-10-12 5.24.07

同時にメールを送信する必要がありますが、テスト用のためメール送信のロジックはコメントしています。(120〜137行目)


エラー表示画面

※すでに登録されているメールアドレスで仮登録しようとした場合、エラーが表示されます。

スクリーンショット 2019-10-12 5.22.34


以上です。お疲れ様でした。


GitHubリポジトリ

今回作成したソースはGithubにて公開しています。下記リンクよりご確認ください。(L5ブランチです。)


読んでいただきありがとうございます。