php+MySQLでセッションを使用したログイン認証

## 参考サイト
http://qiita.com/mpyw/items/bb8305ba196f5105be15

いつも参考にさせていただいてます。

## 機能
```php:functions.php
<?php

/*
 * ログイン状態によってリダイレクト
 * 初回時または失敗時にはヘッダを送信してexitする
 */

function require_unlogined_session () {
    // セッション開始
    @session_start();
    // ログインしていれば
    if (isset($_SESSION["username"])) {
        header('Location: ./index.php');
        exit;
    }
}

function require_logined_session() {
    // セッション開始
    @session_start();
    // ログインしていなければlogin.phpに遷移
    if (!isset($_SESSION["username"])) {
        header('Location: ./login.php');
        exit;
    }
}

// CSRFトークンの生成
function generate_token() {
    // セッションIDからハッシュを生成
    return hash ( 'sha256', session_id() );
}

// CSRFトークン
function validate_token ($token) {
    return $token === generate_token();
}

// htmlspecialchars
function h ($var) {
    if (is_array($var)){
        return array_map(h, $var);
    } else {
        return htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
    }
}
```

## ログイン画面
```php:login.php
<?php

require_once __DIR__ . '/functions.php';
require_unlogined_session();

foreach (['username','password','token','submit'] as $key) {
    $$key = (string)filter_input(INPUT_POST, $key);
}

// エラーを格納する配列を初期化
$errors = [];

// POSTのときのみ実行
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    if ( $username === "" || $password ==="" ) {
        $errors[] = 'ユーザ名またはパスワードが入力されていません。';
    } else {
    
        $username = h($username);
        $password = h($password);
        
        $dbtype  = 'mysql';
        $host    = 'localhost';
        $db      = 'dbname';
        $charset = 'utf8';
 
        $dsn = "$dbtype:host=$host; dbname=$db; charset=$charset";
        $db = new PDO ( $dsn, 'user', 'pass' );
        $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $sql = 'SELECT * FROM user_kaiin WHERE email = ?';
        $prepare = $db->prepare($sql);
        $prepare->bindValue(1, $username, PDO::PARAM_INT);
        $prepare->execute();

        $result = $prepare->fetch(PDO::FETCH_ASSOC);

        if (validate_token(filter_input(INPUT_POST, 'token')) && password_verify( $password, $result["password"] )) {
            // 認証が成功
            // セッションIDの追跡を防ぐ
            session_regenerate_id(true);
            //ユーザ名をセット
            $_SESSION['username'] = $username;
            // ログイン後に/に遷移
            header ('Location: ./index.php');
            exit;
            }
        // 認証が失敗
        $errors[] = "ユーザ名またはパスワードが違います";
    }
}
header ('Content-Type: text/html; charset=UTF-8');
?>

<!DOCTYPE html>
<html>
<head>
<title>ログインページ</title>
</head>
<body>
<h1>ログインしてください</h1>
<?php if ($errors): ?>
<ul>
    <?php foreach ($errors as $err): ?>
    <li><?=h($err)?></li>
    <?php endforeach; ?>
</ul>
<?php endif; ?>
<form method="post" action="">
    <p>ユーザ名: <input type="text" name="username" value="<?php echo $username = isset($_POST['username']) ? $_POST['username']: ''; ?>"></p>
    <p>パスワード: <input type="password" name="password" value=""></p>
    <!-- トークン -->
    <input type="hidden" name="token" value="<?=h(generate_token())?>">    <!--<input type="hidden" name="token" value="<?php echo password_hash('1111', PASSWORD_DEFAULT, array('cost', 10)) ?>">-->
    <p><input type="submit" name="submit" value="ログイン"></p>
</form>
</body>
</html>
```

## ログイン中に閲覧可能な画面
```php:index.php
<?php

require_once __DIR__ . '/functions.php';
require_logined_session();

header ('Content-Type: text/html; charset=UTF-8');

?>

<!DOCTYPE html>
<html>
<head>
<title>会員ページ</title>
</head>
<body>
<h1><?=h($_SESSION["username"])?>としてログインしています</h1>
<p><a href="./logout.php?token=<?=h(generate_token())?>">ログアウト</a></p>
</body>
</html>
```

## ログアウト画面
```php:logout.php
<?php

require_once __DIR__ . '/functions.php';
require_logined_session();

// CSRFトークンを検証
if ( !validate_token(filter_input(INPUT_GET, 'token')) ) {
    // 400 Bad Request
    header ( 'Content-type: text/plain; charset=UTF-8', true, 400 );
    exit('トークンが無効です');
}

// セッション用Cookieの破棄
setcookie(session_name(), '', 1);
// セッションファイルの破棄
session_destroy();
// ログアウト完了後に/login.phpに遷移
header ('Location: ./login.php');
```