一般的でセキュアなログイン機能をPHPで作成しました。

全体像

HTMLフォーム
<form action="" method="post">
<input type="text" name="email" placeholder="ID(メールアドレス)">
<input type="text" name="password" placeholder="パスワード" >
<input type = "hidden" name="token" value="<?php echo $token; ?>">
<input type="submit" name="submit" value="ログイン">
</form>

login.php 

//クッキーから自動ログイン
if(isset($_COOKIE['remember_token'])){
    $remember_token = $_COOKIE['remember_token'];

    $sql = "SELECT * FROM users WHERE remember_token = :remember_token";
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':remember_token',$remember_token);
    $stmt->execute();
    $member = $stmt->fetch();

    if($member){
        $_SESSION['id'] = $member['id'];
        header('Location:../index.php');
        exit;
    }
}

//トークン発行
if(!isset($_SESSION['token'])){
    $_SESSION['token'] = bin2hex(random_bytes(32)); 
}
$token = $_SESSION['token'];

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    if(!isset($_POST['token']) || $_POST['token'] !== $token){
        $_SESSION['errMsg'] = "不正なリクエストです。";
        header('Location:login.php');
        exit;
    }

    $email = isset($_POST['email'])?$_POST['email']:null;
    $password = isset($_POST['password'])?$_POST['password']:null;

    $sql = "SELECT * FROM users WHERE email = :email";
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':email',$email);
    $stmt->execute();
    $member = $stmt->fetch();

    if($member && password_verify($password,$member['password'])){

        session_regenerate_id(true);
        $_SESSION['id'] = $member['id'];
    
    if(isset($_POST['remember_me'])){
        $remember_token = bin2hex(random_bytes(32));

        setcookie('remember_token',$remember_token,time()+60*60*24*14,
        "/","",isset($_SERVER['HTTPS']),true);

        $sql = "UPDATE users SET remember_token = :remember_token WHERE id = :id";
        $stmt = $dbh->prepare($sql);
        $stmt->bindValue(':remember_token',$remember_token);
        $stmt->bindValue(':id',$member['id']);
        $stmt->execute();
    }
    header('Location:../index.php');
    exit;
    }else{
        $errMsg = "メールアドレスまたはパスワードが正しくありません。";
    }
}

logout.php 


if(ini_get("session.use_cookies")){
    $params = session_get_cookie_params();
    setcookie(session_name(),'',time()-42000,
    $params['path'],$params['domain'],
    $params['secure'],$params['httponly']
);
}

if(isset($_COOKIE['remember_token'])){
    
    setcookie('remember_token','',time()-42000,"/");
}
    if(isset($_SESSION['id'])){
        $sql = "UPDATE users SET remember_token = null WHERE id = :id";
        $stmt = $dbh->prepare($sql);
        $stmt->bindValue(':id', $_SESSION['id']);
        $stmt->execute();
    }

$_SESSION = array();
session_destroy();


header('Location:./index.php');
exit;

説明

HTMLフォーム

<form action="" method="post">
<input type="text" name="email" placeholder="ID(メールアドレス)">
<input type="text" name="password" placeholder="パスワード" >
<input type = "hidden" name="token" value="<?php echo $token; ?>">
<input type="submit" name="submit" value="ログイン">
</form>

メールとパスワードを入力して、hiddenでトークンをPOSTでlogin.phpに渡す。

ログインボタンを押してフォームを送信する。

login.php ログイン用

自動ログイン

//クッキーから自動ログイン
if(isset($_COOKIE['remember_token'])){
    $remember_token = $_COOKIE['remember_token'];

    $sql = "SELECT * FROM users WHERE remember_token = :remember_token";
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':remember_token',$remember_token);
    $stmt->execute();
    $member = $stmt->fetch();

    if($member){
        $_SESSION['id'] = $member['id'];
        header('Location:../index.php');
        exit;
    }
}

自動ログイン用のコードです。2週間ログイン情報が維持されるようになっています。

$remember_tokenをログインしているユーザーのデータベースから探し出して、

idを取得してセッションとして渡してログイン状態にする。

ログインのためのトークン発行

//トークン発行
if(!isset($_SESSION['token'])){
    $_SESSION['token'] = bin2hex(random_bytes(32)); 
}
$token = $_SESSION['token'];

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    if(!isset($_POST['token']) || $_POST['token'] !== $token){
        $_SESSION['errMsg'] = "不正なリクエストです。";
        header('Location:login.php');
        exit;
    }

ランダムでトークンを発行して、変数化してHTMLフォームのhidden内に置いて、再びlogin.phpに値を渡す。

トークンが確認されない場合、ログイン画面に移動するようになっています。

   $email = isset($_POST['email'])?$_POST['email']:null;
    $password = isset($_POST['password'])?$_POST['password']:null;

    $sql = "SELECT * FROM users WHERE email = :email";
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':email',$email);
    $stmt->execute();
    $member = $stmt->fetch();

    if($member && password_verify($password,$member['password'])){

        session_regenerate_id(true);
        $_SESSION['id'] = $member['id'];
    
    if(isset($_POST['remember_me'])){
        $remember_token = bin2hex(random_bytes(32));

        setcookie('remember_token',$remember_token,time()+60*60*24*14,
        "/","",isset($_SERVER['HTTPS']),true);

        $sql = "UPDATE users SET remember_token = :remember_token WHERE id = :id";
        $stmt = $dbh->prepare($sql);
        $stmt->bindValue(':remember_token',$remember_token);
        $stmt->bindValue(':id',$member['id']);
        $stmt->execute();
    }
    header('Location:../index.php');
    exit;
    }else{
        $errMsg = "メールアドレスまたはパスワードが正しくありません。";
    }
}

HTMLフォームからPOSTされたメールとアドレスを受け取る。

データベースからメールとパスワードに合致するidを見つける。

そのidをセッションとしてログイン先に次のように渡してログインを維持させる。

ログイン先 index.php

if(isset($_SESSION['id'])){
    $id = $_SESSION['id'];
}else{
    header('Location:login.php');
        exit;
}

もしremember_meが存在したら自動ログイン用トークンを発行する。

setcookieでトークンに期限を持たせてログインしているユーザーのデータに期限トークン保存する。

logout.php

if(ini_get("session.use_cookies")){
    $params = session_get_cookie_params();
    setcookie(session_name(),'',time()-42000,
    $params['path'],$params['domain'],
    $params['secure'],$params['httponly']
);
}

if(isset($_COOKIE['remember_token'])){
    
    setcookie('remember_token','',time()-42000,"/");
}
    if(isset($_SESSION['id'])){
        $sql = "UPDATE users SET remember_token = null WHERE id = :id";
        $stmt = $dbh->prepare($sql);
        $stmt->bindValue(':id', $_SESSION['id']);
        $stmt->execute();
    }

$_SESSION = array();
session_destroy();


header('Location:./index.php');
exit;

ログイン維持の状態を解除する。

自動ログイン用トークンがあった場合、

ログインしているユーザーのデータベースからトークンの状態をnullにする。

セッション情報を破棄して、ログイン情報を削除する。