Skip to content

Instantly share code, notes, and snippets.

@joshuaadickerson
Created January 13, 2016 23:16
Show Gist options
  • Save joshuaadickerson/142f76c344a998b70961 to your computer and use it in GitHub Desktop.
Save joshuaadickerson/142f76c344a998b70961 to your computer and use it in GitHub Desktop.
Mock example of what changes would be made to use tokens instead of password hashes for the login cookie
This is just an example. It's not meant to work.
I commented where I thought it needed to be commented but you should just be paying attention to the differences between the new and old class.
I did not comment the methods or properties because I didn't want you to worry about that. It doesn't really matter what types go in and out.
The names of the methods are self-explanatory.
<?php
abstract class AbstractLogin
{
protected $login_cookie = 'MYSITE_LOGIN';
// This is just for mocking. This would be a database request
protected $users = [
1 => 'hashed(foopass)',
15 => 'hashed(pass)',
22 => 'hashed(foobarpass)',
];
// This is just for mocking. This would be a database request
protected $login_attempts = [];
// This is just for mocking. This would be a database request
protected $auth_attempts = [];
protected $max_login_attempts = 3;
protected $max_auth_attempts = 20;
protected function checkLogin($id, $password)
{
return isset($this->users[$id]) && $this->users[$id] === $password;
}
protected function hash($pass, $salt = '')
{
return 'hashed(' . $pass . $salt . ')';
}
protected function removeCookie($name)
{
unset($_COOKIE[$name]);
setcookie($name, null, -1);
}
public function login($id, $password)
{
$id = (int) $id;
$hash = $this->hash($password);
$authenticated = false;
if ($this->checkLogin($id, $hash))
{
$this->loginSuccess($id, $hash);
$authenticated = true;
}
return $authenticated;
}
abstract public function logout($id);
abstract public function closeUserSession($token);
abstract protected function logFailedAuthentication($id);
abstract protected function parseLoginCookie();
abstract protected function loginSuccess($id, $hash);
public function authenticate()
{
$authenticated = false;
if (isset($_COOKIE[$this->login_cookie]))
{
list($id, $hash) = $this->parseLoginCookie();
if ($this->checkLogin($id, $hash))
{
$authenticated = true;
}
else
{
$this->removeCookie($this->login_cookie);
// note: no brute-force protection
$this->logFailedAuthentication($id);
trigger_error('User ' . $id . ' attempted to authenticate with a cookie but failed');
}
}
return $authenticated;
}
}
<?php
class NewLogin extends AbstractLogin
{
// serialize() is dangerous and for 2 string values, we don't really need JSON
protected $cookie_delimiter = "\n|||\n";
// This would be a table in a database
protected $sessions = [
// The key is the token and would be the PK in the database
'ea729ad910c3ccdb10' => ['id' => 15, 'time' => 1234567890, 'length' => 5000, 'agent' => 'Chrome'],
];
// Get all of the users that are logged in
public function getUsersWithOpenSessions()
{
return array_keys($this->sessions);
}
// You can log a user out at any time with this
// Notice the token is used because you can log a user out of individual devices
public function closeUserSession($token)
{
unset($this->sessions[$token]);
}
protected function loginSuccess($id, $pass_hash)
{
// What if we encrypted the id? Note: not hashing, but encryption so that we can restore it
// Or, instead of using their id, use their non-public username.
// The reason is so you'd have to guess a user's login name to cause a brute-force logout
setcookie($this->login_cookie, $id . $this->cookie_delimiter . $this->createToken());
}
protected function createToken()
{
return $this->randomString();
}
protected function randomString()
{
// random_bytes() is only in PHP 7 so pick a method of creating a random string
// this would register the token in the database with the user's id
return bin2hex(random_bytes());
}
public function logout($id)
{
$this->removeCookie($this->login_cookie);
// We track that the user is logged in so we have to remove it.
// We track each session separately, so pressing logout logs them out of all devices
// If you want to close just one session, use the closeUserSession() method
// In reality this would be a simple DELETE query
foreach ($this->sessions as $token => $info)
{
if ($info['id'] === $id)
{
unset($this->sessions[$token]);
}
}
}
protected function logFailedAuthentication($id)
{
if (!isset($this->auth_attempts[$id]))
{
$this->auth_attempts[$id] = [];
}
// In reality we'd log a lot about this attempt
$this->auth_attempts[$id][] = microtime(true);
// If they passed the threshold we logout the user so they have to try a new token and start all over
if ($this->auth_attempts[$id] > $this->max_auth_attempts)
{
$this->closeUserSession($id);
}
trigger_error('Failed authentication on user ' . $id);
}
protected function parseLoginCookie()
{
$return = array('id' => null, 'hash' => null);
if (isset($_COOKIE[$this->login_cookie])) {
$return = explode($this->cookie_delimiter, $_COOKIE[$this->login_cookie]);
}
return $return;
}
}
<?php
class OldLogin extends AbstractLogin
{
protected function loginSuccess($id, $pass_hash)
{
setcookie($this->login_cookie, serialize(array('id' => (int) $id, 'password' => $pass_hash)), 5000);
}
protected function logFailedAuthentication($id)
{
// All we can do is log it. They can keep on trying but we can't change anything.
// We can't change their password
// We can make it so they are locked out but that makes it simple for someone to lock everyone out
// If the user is an admin, what do we do?
trigger_error('Failed authentication on user ' . $id);
}
protected function parseLoginCookie()
{
$return = array('id' => null, 'hash' => null);
if (isset($_COOKIE[$this->login_cookie])) {
$return = unserialize($_COOKIE[$this->login_cookie]);
}
return $return;
}
public function closeUserSession($token)
{
throw new \Exception('Closing a user session is not supported.');
}
public function logout($id)
{
$this->removeCookie($this->login_cookie);
// No tracking of the user's session. No need to remove anything
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment