I have a login system for my website that works as follows:
In the database I have a table called users
with the fields user
, password
and authentication_token
.
user
gets set manually by request in the database.
When a user activates his account, the following script stores his password and an authentication_token
to the matching user
in the database:
// store passwort and authentication_token when user activtes account
$password = password_hash($_POST['password'], PASSWORD_DEFAULT)
$authentication_token = bin2hex(random_bytes(64));
$stmt = $pdo->prepare("UPDATE users SET password = :password, authentication_token = :authentication_token WHERE user = :user");
$stmt->execute(array(':user' => $_POST['user'], ':password' => $password, ':authentication_token' => $authentication_token));
password
contains the hash of the password inserted by the user, created with the bcrypt
algorithm.
authentication_token
contains a unique string with a length of 128 characters.
Every page, that should only be accessible with login, calls the following script named login_check.php
, that is placed at the very top of each page:
// get login data from database
if(isset($_COOKIE['user'])) {
$stmt = $pdo->prepare('SELECT authentication_token FROM users WHERE user = :user;');
$stmt->execute(array(':user' => $_COOKIE['user']));
$results = $stmt->fetchAll();
foreach($results as $row) {
$authentication_token = $row['authentication_token'];
}
}
// show login form if no data or wrong data
if(empty($_COOKIE['user']) || empty($_COOKIE['authentication_token']) || $_COOKIE['authentication_token'] != $authentication_token) {
include "login.php";
exit;
}
This will check if the user
and authentication_token
stored as cookies match to the data in the users
table.
If yes, the script does nothing else and the site gets presented.
If not, the login form page gets included and all further code on this page gets aborted with the exit;
command.
The login form page, called login.php
, that gets included in case user
and authentication_token
stored as cookies don't match to the database, is the following:
// get login data from database
if(isset($_POST['user'])) {
$stmt = $pdo->prepare('SELECT password, authentication_token FROM users WHERE user = :user LIMIT 1;');
$stmt->execute(array(':user' => isset($_POST['user']));
$results = $stmt->fetchAll();
foreach($results as $row) {
$password = $row['password'];
$authentication_token = $row['authentication_token'];
}
}
// show login form if no data or wrong data
if(empty($_COOKIE['authentication_token']) || $_COOKIE['authentication_token'] != $authentication_token) {
echo "
<form action=\"\" method=\"post\">
<input type=\"text\" name=\"user\">
<input type=\"password\" name=\"password\">
</form>
";
}
// login
if(isset($_POST['user'], $_POST['password']) && password_verify($_POST['password'], $password)) {
setcookie("user", $_POST['user'], time()+(10*365*24*60*60), "/");
setcookie("authentication_token", $authentication_token, time()+(10*365*24*60*60), "/");
unset($_POST);
header('location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
exit;
}
This code will first show the login form. As soon, as user
and password
, that get submitted with the form, match to the data in the database, user
and authentication_token
get stored as cookies and the page will get reloaded.
This will again first include the login_check.php
. This will check again if user
and authentication_token
, that are stored as cookies, match to the database. If yes, what is the case now, the page won't get blocked with exit;
any more and is accessible.
Is the described scenario recommended? Is this a good way to do that?
Is there anything I made wrong or I missed?
I also have the following script in the login_check.php
file, that handles the logout process if the user presses on the logout button:
if($_POST['logout']) {
unset($_COOKIE['user']);
unset($_COOKIE['authentication_token']);
setcookie('user', '', time() - 3600, "/");
setcookie('authentication_token', '', time() - 3600, "/");
unset($_POST);
header('location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
exit;
}
But I don't really rate the logout function as security feature, because users may not use the logout button.
I think my code is pretty safe against hacking (assumed that the passwords are safe enough). My code is lightweight and clear. I'm using secure hashes. No password gets stored on the clients side. I'm only using prepared statements. The authentication_token
is unique.
The only thing I'm concerning about is the following: What if the user doesn't use his own device and doesn't log out? Since the authentication_token
is always the same, anybody else could visit the pages without the need to have the login data, since it's already logged in and the login doesn't expire.
How do you handle this case typically? Change the authentication_token
stored in the database frequently, to force the user to login again from time to time? Change the authentication_token
at every new login on another device? Set a cookie expiration time of few hours?