Created
January 13, 2016 23:16
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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