PHP+MySQLで簡易ログインシステムを作る

動作環境

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

作成したデータベースへの接続を定義します。

 

db_connect.php

<?php

/* @ データベースの接続情報を定数に格納する */

const DB_HOST = 'mysql:dbname=user_login;host=localhost';

const DB_USER = 'root';

const DB_PASSWORD = 'root';

 

//A 例外処理を使って、DBPDO接続する

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

複数のファイルで共通して使うユーザー定義関数を書いていきます。

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

ユーザーの新規登録画面を作っていきます。

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

続いてユーザーのログイン画面を作っていきます。

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

ログイン後のウェルカムページを作ります。

 

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;