動作環境
MAMP
PHP 7.4.21
MySQL 5.7.34
今回実装するディレクトリの構成は下記の通りです。
root/
├ db_connect.php
//
データベースの接続用
├ functions.php //
ユーザー定義関数
├ register.php //
新規登録処理
├ login.php //
ログイン処理
├welcome.php //
ログイン後のウェルカムページ
└logout.php //
ログアウト処理
DB作成:user_login
mysql> CREATE DATABASE user_login;
テーブル作成
CREATE TABLE users (id
INT NOT NULL PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL UNIQUE,password VARCHAR(255) NOT NULL,created_at DATETIME DEFAULTCURRENT_TIMESTAMP);
作成したデータベースへの接続を定義します。
db_connect.php
<?php
/* @ データベースの接続情報を定数に格納する
*/
const DB_HOST = 'mysql:dbname=user_login;host=localhost';
const DB_USER = 'root';
const DB_PASSWORD = 'root';
//A 例外処理を使って、DBにPDO接続する
try {
$pdo = new PDO(DB_HOST,DB_USER,DB_PASSWORD,[
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES =>false
]);
} catch (PDOException
$e) {
echo 'ERROR:
Could not connect.'.$e->getMessage()."\n";
exit();
}
複数のファイルで共通して使うユーザー定義関数を書いていきます。
functions.php
<?php
//XSS対策
function h($s){
return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
}
//セッションにトークンセット
function setToken(){
$token = sha1(uniqid(mt_rand(), true));
$_SESSION['token'] =
$token;
}
//セッション変数のトークンとPOSTされたトークンをチェック
function checkToken(){
if(empty($_SESSION['token'])
|| ($_SESSION['token'] !=
$_POST['token'])){
echo 'Invalid
POST', PHP_EOL;
exit;
}
}
//POSTされた値のバリデーション
function validation($datas,$confirm
= true)
{
$errors = [];
//ユーザー名のチェック
if(empty($datas['name']))
{
$errors['name'] = 'Please
enter username.';
}else if(mb_strlen($datas['name'])
> 20)
{
$errors['name'] = 'Please
enter up to 20 characters.';
}
//パスワードのチェック(正規表現)
if(empty($datas["password"])){
$errors['password'] = "Please
enter a password.";
}else if(!preg_match('/\A[a-z\d]{8,100}+\z/i',$datas["password"])){
$errors['password'] = "Please
set a password with at least 8 characters.";
}
//パスワード入力確認チェック(ユーザー新規登録時のみ使用)
if($confirm){
if(empty($datas["confirm_password"])){
$errors['confirm_password'] = "Please
confirm password.";
}else if(empty($errors['password'])
&& ($datas["password"] != $datas["confirm_password"])){
$errors['confirm_password'] = "Password
did not match.";
}
}
return
$errors;
}
ユーザーの新規登録画面を作っていきます。
register.php
<?php
//ファイルの読み込み
require_once "db_connect.php";
require_once "functions.php";
//セッションの開始
session_start();
//POSTされてきたデータを格納する変数の定義と初期化
$datas = [
'name' => '',
'password' => '',
'confirm_password' => ''
];
//GET通信だった場合はセッション変数にトークンを追加
if($_SERVER['REQUEST_METHOD'] != 'POST'){
setToken();
}
//POST通信だった場合はDBへの新規登録処理を開始
if($_SERVER["REQUEST_METHOD"] == "POST"){
//CSRF対策
checkToken();
//
POSTされてきたデータを変数に格納
foreach($datas as
$key => $value) {
if($value
= filter_input(INPUT_POST,
$key, FILTER_DEFAULT)) {
$datas[$key] =
$value;
}
}
//
バリデーション
$errors = validation($datas);
//データベースの中に同一ユーザー名が存在していないか確認
if(empty($errors['name'])){
$sql = "SELECT
id FROM users WHERE name = :name";
$stmt = $pdo->prepare($sql);
$stmt->bindValue('name',$datas['name'],PDO::PARAM_INT);
$stmt->execute();
if($row
= $stmt->fetch(PDO::FETCH_ASSOC)){
$errors['name'] = 'This
username is already taken.';
}
}
//エラーがなかったらDBへの新規登録を実行
if(empty($errors)){
$params = [
'id' =>null,
'name'=>$datas['name'],
'password'=>password_hash($datas['password'],
PASSWORD_DEFAULT),
'created_at'=>null
];
$count = 0;
$columns = '';
$values = '';
foreach (array_keys($params) as
$key) {
if($count > 0){
$columns .= ',';
$values .= ',';
}
$columns .=
$key;
$values .= ':'.$key;
$count++;
}
$pdo->beginTransaction();//トランザクション処理
try {
$sql = 'insert
into users ('.$columns .')values('.$values.')';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$pdo->commit();
header("location: login.php");
exit;
} catch (PDOException
$e) {
echo 'ERROR:
Could not register.';
$pdo->rollBack();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sign
Up</title>
<!--
bootstrap読み込み
-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body{
font: 14px sans-serif;
}
.wrapper{
width: 400px;
padding: 20px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="wrapper">
<h2>Sign
Up</h2>
<p>Please
fill this form to create an account.</p>
<form action="<?php echo
$_SERVER ['SCRIPT_NAME']; ?>" method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="name" class="form-control
<?php echo (!empty(h($errors['name'])))
? 'is-invalid' : ''; ?>" value="<?php echo h($datas['name']);
?>">
<span class="invalid-feedback"><?php echo h($errors['name']);
?></span>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control
<?php echo (!empty(h($errors['password'])))
? 'is-invalid' : ''; ?>" value="<?php echo h($datas['password']);
?>">
<span class="invalid-feedback"><?php echo h($errors['password']);
?></span>
</div>
<div class="form-group">
<label>Confirm
Password</label>
<input type="password" name="confirm_password" class="form-control
<?php echo (!empty(h($errors['confirm_password']))) ? 'is-invalid' : ''; ?>" value="<?php echo h($datas['confirm_password']);
?>">
<span class="invalid-feedback"><?php echo h($errors['confirm_password']); ?></span>
</div>
<div class="form-group">
<input type="hidden" name="token" value="<?php echo h($_SESSION['token']);
?>">
<input type="submit" class="btn btn-primary" value="Submit">
</div>
<p>Already have an account? <a href="login.php">Login
here</a>.</p>
</form>
</div>
</body>
</html>
続いてユーザーのログイン画面を作っていきます。
login.php
<?php
//ファイルの読み込み
require_once "db_connect.php";
require_once "functions.php";
//セッション開始
session_start();
// セッション変数
$_SESSION["loggedin"]を確認。ログイン済だったらウェルカムページへリダイレクト
if(isset($_SESSION["loggedin"]) &&
$_SESSION["loggedin"] === true){
header("location: welcome.php");
exit;
}
//POSTされてきたデータを格納する変数の定義と初期化
$datas = [
'name' => '',
'password' => '',
'confirm_password' => ''
];
$login_err = "";
//GET通信だった場合はセッション変数にトークンを追加
if($_SERVER['REQUEST_METHOD'] != 'POST'){
setToken();
}
//POST通信だった場合はログイン処理を開始
if($_SERVER["REQUEST_METHOD"] == "POST"){
////CSRF対策
checkToken();
//
POSTされてきたデータを変数に格納
foreach($datas as
$key => $value) {
if($value
= filter_input(INPUT_POST,
$key, FILTER_DEFAULT)) {
$datas[$key] =
$value;
}
}
//
バリデーション
$errors = validation($datas,false);
if(empty($errors)){
//ユーザーネームから該当するユーザー情報を取得
$sql = "SELECT
id,name,password FROM users WHERE name = :name";
$stmt = $pdo->prepare($sql);
$stmt->bindValue('name',$datas['name'],PDO::PARAM_INT);
$stmt->execute();
//ユーザー情報があれば変数に格納
if($row
= $stmt->fetch(PDO::FETCH_ASSOC)){
//パスワードがあっているか確認
if (password_verify($datas['password'],$row['password']))
{
//セッションIDをふりなおす
session_regenerate_id(true);
//セッション変数にログイン情報を格納
$_SESSION["loggedin"] = true;
$_SESSION["id"] =
$row['id'];
$_SESSION["name"] = $row['name'];
//ウェルカムページへリダイレクト
header("location:welcome.php");
exit();
} else {
$login_err = 'Invalid
username or password.';
}
}else {
$login_err = 'Invalid
username or password.';
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body{
font: 14px sans-serif;
}
.wrapper{
width: 400px;
padding: 20px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="wrapper">
<h2>Login</h2>
<p>Please
fill in your credentials to login.</p>
<?php
if(!empty($login_err)){
echo '<div
class="alert alert-danger">' . $login_err . '</div>';
}
?>
<form action="<?php echo
$_SERVER ['SCRIPT_NAME']; ?>" method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="name" class="form-control
<?php echo (!empty(h($errors['name'])))
? 'is-invalid' : ''; ?>" value="<?php echo h($datas['name']);
?>">
<span class="invalid-feedback"><?php echo h($errors['name']);
?></span>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control
<?php echo (!empty(h($errors['password'])))
? 'is-invalid' : ''; ?>" value="<?php echo h($datas['password']);
?>">
<span class="invalid-feedback"><?php echo h($errors['password']);
?></span>
</div>
<div class="form-group">
<input type="hidden" name="token" value="<?php echo h($_SESSION['token']);
?>">
<input type="submit" class="btn btn-primary" value="Login">
</div>
<p>Don't have an account? <a href="register.php">Sign
up now</a></p>
</form>
</div>
</body>
</html>
ログイン後のウェルカムページを作ります。
welcome.php
<?php
session_start();
// セッション変数
$_SESSION["loggedin"]を確認。ログイン済だったらウェルカムページへリダイレクト
if(!isset($_SESSION["loggedin"]) ||
$_SESSION["loggedin"] !== true){
header("location: login.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body{
font: 14px sans-serif;
text-align: center;
}
</style>
</head>
<body>
<h1 class="my-5">Hi,<b><?php echo htmlspecialchars($_SESSION["name"]);
?></b>.
Welcome to our site.</h1>
<p>
<a href="index2.php" class="btn btn-danger ml-3">Sign
Out of Your Account</a>
</p>
</body>
</html>
logout.php
最後にログアウト処理を実装します。
logout.php
<?php
session_start();
//セッション変数の削除
$_SESSION = array();
//セッション削除
session_destroy();
//ログインページへリダイレクト
header("location: login.php");
exit;