What I try to achieve
I need to create a administration-panel for a website. Therefore, I need a possibility to protect the content of the panel via a password. The functionality doesn't have to be very advanced. I neither need a password-reset functionality nor the possibility to add more than one user. I also don't need a "keep me logged in"-functionality.
It is also a pretty small site, so I don't think the security standards need to be as high as with big companies.
I can't use cookies for this project. It is for a completely "cookie-free" website.
I've already asked a question about this on stackoverflow, where user Ro Achterberg suggested in a comment that I could add layers for improving the security.
I therefore decided that I add 2 factor authentication via email.
The code
index.php
<?php
//Documentation: https://www.php.net/manual/de/function.password-hash.php
$username = '$2y$10$bWW0KD6P7WUaTU99PpcjtON1xKSBhCCxxiiyoaMuY0aVehZSfgVI6'; //result of password_hash("admin", PASSWORD_BCRYPT);
$password = '$2y$10$NxtrHFdZGZMG7y2G6l2o6eZpksOQfQvrQrCBTj7knEmL8VynQlcz2'; //result of password_hash("1234", PASSWORD_BCRYPT);
//Loading login form
$content = "<h1>Login</h1>
<form method='POST' class='input'>
<label>
<input type='text' name='username' placeholder='Username' required/>
</label>
<label>
<input type='password' name='password' placeholder='Password' required/>
</label>
<input type='submit' name='login' value='Login'/>
</form>";
//Check if form was submitted
if(isset($_POST['login'])) {
//Documentation: https://www.php.net/manual/de/function.password-verify.php
$verifyUsername = password_verify($_POST['username'], $username);
$verifyPassword = password_verify($_POST['password'], $password);
//Check if user entered correct username and password
if($verifyUsername && $verifyPassword) {
//Generate and send OTP
include "randomCode.php";
$code = randomCode();
mail("[email protected]", "One time code", "Your one time code: " . $code);
$time = openssl_encrypt(time(),"AES-128-ECB", "****************");
$code = password_hash($code, PASSWORD_BCRYPT) . $time;
//Load 2fa screen
$content = "<h1>2-factor-authentication</h1>
<form method='POST' class='input'>
<label>
<input type='text' name='2fa' placeholder='one time code' class='validInput' required/>
</label>
<input id='code' name='code' type='text' value='$code' readonly/>
<input type='submit' name='2faLogin' value='Login'/>
</form>";
}
//Reload login form
else {
$content = "<h1>Login</h1>
<p>Wrong username or password!</p>
<form method='POST' class='input'>
<label>
<input type='text' name='username' placeholder='Username' class='validInput' required/>
</label>
<label>
<input type='password' name='password' placeholder='Password' class='validInput' required/>
</label>
<input type='submit' name='login' value='Login'/>
</form>";
}
}
//Check if one time code was submitted
if(isset($_POST['2faLogin'])) {
$time = openssl_decrypt(substr($_POST['code'], 60), "AES-128-ECB", "****************");
//Did user take too long to enter code (5 minutes)?
if(!$time || time() - intval($time) > 300) {
include "randomCode.php";
$code = randomCode();
mail("[email protected]", "One time code", "Your one time code: " . $code);
$time = openssl_encrypt(time(),"AES-128-ECB", "****************");
$code = password_hash($code, PASSWORD_BCRYPT) . $time;
$content = "<h1>2-factor-authentication</h1>
<p>Code invalid! We've sent you a new mail.</p>
<form method='POST' class='input'>
<label>
<input type='text' name='2fa' placeholder='one time code' class='validInput' required/>
</label>
<input id='code' name='code' type='text' value='$code' readonly/>
<input type='submit' name='2faLogin' value='Login'/>
</form>";
}
//Did user was too fast (10 seconds)?
else if(time() - intval($time) < 10) {
$code = $_POST['code'];
$content = "<h1>2-factor-authentication</h1>
<p>That was a bit too fast.</p>
<form method='POST' class='input'>
<label>
<input type='text' name='2fa' placeholder='one time code' class='validInput' required/>
</label>
<input id='code' name='code' type='text' value='$code' readonly/>
<input type='submit' name='2faLogin' value='Login'/>
</form>";
}
//Time is ok
else {
$verify = password_verify($_POST['2fa'], substr($_POST['code'], 0, 60));
//Verify OTP failed
if(!$verify) {
$code = $_POST['code'];
$content = "<h1>2-factor-authentication</h1>
<p>Entered wrong code!</p>
<form method='POST' class='input'>
<label>
<input type='text' name='2fa' placeholder='one time code' class='validInput' required/>
</label>
<input id='code' name='code' type='text' value='$code' readonly/>
<input type='submit' name='2faLogin' value='Login'/>
</form>";
}
//Verified OTP successfully
else {
$content = "<h1>Welcome!</h1><p>Content here...</p>";
}
}
}
?>
<!DOCTYPE html>
<html lang='en'>
<head>
<title>John Doe</title>
<meta http-equiv='Content-Type' content='text/html;charset=utf-8'/>
</head>
<body id='body'>
<?php echo $content; ?>
</body>
</html>
randomCode.php
<?php
function randomCode() {
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$random = "";
for($i = 0; $i < 6; $i++) {
$random .= $characters[rand(0, strlen($characters) - 1)];
}
return $random;
}
Question
All suggestions are welcome. I am especially interested in the security of this code and how to further improve it.