Logging in wth password_hash() and sessions

When using the old fashioned sha/md5 salted password hashing, you could store the username and password in a session e.g.

$_SESSION['username'] = $_POST['username'];
$_SESSION['password'] = sha1($salt . $_POST['password']);

The advantage of this is that on every request you can query the database WHERE username= :username AND password = :password' where the variables come from the session. If the account is deleted or the password is changed, the user is logged out. Any user who thinks their account is compromised can change their password any anyone else using the account is logged out.

With password_verify this is quite difficult because it’s a boolean operation. It’s possible to do it like this:

$_SESSION['username'] = $_POST['username'];
$_SESSION['password'] = $_POST['password'];

if (password_verify($_SESSION['password'], $hashFromDatabase)) {
//logged in
}
else {
//destroy session and forward to error/login page
}

While this works, it requires storing the password in plaintext in the session. The only way I can see to avoid this is hashing passwords twice. So instead of

$hashedPass = password_hash($_POST['password'], PASSWORD_DEFAULT);

You’d have:

$hashedPass = password_hash(sha1($_POST['password']), PASSWORD_DEFAULT);

That way, when someone logs in you can use $_SESSION['password'] = sha1($_POST['password']); and then pass that to password_verify.

Alternatively, extract the hash from the database and store it in the session, checking the old hash vs the new hash on every page load e.g on login:


if (password_verify($_POST['password'], $hashFromDatabase)) {
$_SESSION['username'] = $usernameFromDatabase;
$_SESSION['password'] = $hashFromDatabase;
}

and then on each request:

$user = $pdo->query('SELECT password FROM users WHERE username = ' . $_SESSION['username']);

if ($user->password != $_SESSION['password']) {
 //password has been changed, log out.
}

This seems very long winded, is there a more efficient way to do this while maintaining the feature where changing the password causes other browsers to be logged out and not storing the plaintext password in a session?

Store a checksum instead of the actual hashed password or set an isChanged flag or something.

The hashing of the password should be as expensive as you can get away with, so checking this every time twice should be very expensive.

Plus, probably insecure in a number of ways. Ask Ashley Madison why double hashing passwords using methods other than standard BCrypt is a bad idea. If you want to increase the security of the hashed password, increase the rounds, don’t mess with the hash itself.

Carry a session “ID” that you track on login, then when the password is changed, all open sessions (for those of us that use multiple machines) are deleted for that user?

I can see that it’s far from ideal, I was hoping since password_hash is so common there would be a best practice solution to this, it’s been solved before so I was wondering what the best approach here is.

Another option I thought of is, on login:

$_SESSION['token'] = sha1($usernameFromDatabase . $hashFromDatabase);

then on each request:

$user = $pdo->query('SELECT password FROM users WHERE username = ' . $_SESSION['username']);

if (sha1($user->username . $user->password) !== $_SESSION['token']) {
 //log out
}

This would also work but this must be a solved problem so I’m guessing there is a standard best practice way to handle this.

With my own setup when someone logs in, it stores the id of the user as a field in the sessions table so if someone changes their password on one computer I could just delete all sessions for that user that don’t have the session id of the session they were using when they changed the password. I would then regenerate their session id. It saves me having to store the password in the session data

Store the checksum of the hashed password. It’s just an integer to verify integrity. The chances of collision would be exceptionally low, but if there was, it’s not really a critical issue, you just wanted the user to log out.

$_SESSION['pass_checksum'] = crc32("\$2a\$12\$zMqE\.FdtSPo\.3\.HoMc3GT\.eDSrbzbLCzIHD9Q461E7kpES3rkuDFW");

So that way 305780430 is stored and you just have to check that, instead of rehashing the password and verifying it (which should be much more expensive).

Probably need to cast the checksum result as a string or whatever, I’m not sure how PHP would handle that.

edit: ignore that I misread what you put.

Is there any advantage of the checksum here? Why not just use

$_SESSION['pass'] = "\$2a\$12\$zMqE\.FdtSPo\.3\.HoMc3GT\.eDSrbzbLCzIHD9Q461E7kpES3rkuDFW";

Where the password string comes from the database? Does crc32 add any security benefit?

Double hashing dramatically reduces the number of possible values that need to be checked to find a working password as each hashing does a many to one mapping of initial values to hashes.

Passing passwords (hashed or not) in the session also reduces the security of the system and makes it more vulnerable to attack.

A better alternative would be to generate a nonce when the person logs in and pass that in the session in place of the password. The nonce can then be discarded when the person logs out so that unlike a password the value will be useless to anyone once the current login session has ended.

Yeah, that is a BCrypt string (the algorithm behind password_hash). Even though I’m not really a php developer it’s a generic algorithm and the PHP.net FAQ page for it is actually my favorite explanation.

The security benefit from the checksum, would be that you’re not storing the passwords in memory or doing anything else to them that’ll cause collisions and end up with something that can be broken because of it (like in the case of Ashley Madison). It would be much faster to use it over re-checking the hash too, since the hash should be as expensive as your system can handle efficiently (usually 12 to 14 rounds)… that said, I guess you could just compare the strings. Which it looks like you’re doing above (now that I looked at it again :-D).

Another way would be that if you’re relying on sessions anyway, then just populate them with the last login and last update timestamps. if($lastupdate > $lastlogin) { logout(); }. That way you can just store that when it’s updated, instead of rechecking the db on every request. You’re probably keeping those values in the DB anyway.

Basically, any way you can avoid mucking with the actual password at all. DB lookups and hashing are expensive.

Why can’t you just do…

$_SESSION['password'] = $hashFromDatabase;

...

WHERE username= :username_from_session AND password = :password_from_session
1 Like

There is several ways to solve this “problem”, the system we have found that work the best is to keep a link table to the user table, where on a successful login a new record is stored together with device information. The key for this record is then stored in the SESSION together with the user account id. Note. With this approach it is important that it is required that the password is entered on the sections where the user can change their account information.

Every X% page loads we check that the login is still valid, mt_rand with the frequency you want. Note. Checking on every page load is a waste of resources.

When something happen, i.e. new login, password change, account is disabled etc. the system can handle any active records, and mark them as not active. Then on the first check, these accounts would be logged out.

The reason we find this the best approach is due to it really work well in todays environment where content is served from an API to multiple locations, i.e. website, different native mobile apps etc. and it makes it very easy to for example allow a user to be logged in from the website and mobile app at the same time, but never more than one login on each. It also makes it possible to show login history to the user, as well as allowing them to whitelist devices like their cellphone etc.

That’s one of the options I presented earlier, however felgall said:

Is there a reason not to store hashed passwords in the session? Anyone with access to the sessions has access to the server, and therefore access to the database and the hashed passwords anyway.

This is quite a robust solution but does add complexity. Without needing to keep track of logged in devices I was hoping for the security without quite as much complexity or the need for an extra database table.

Except it doesn’t have to be long-winded, like you were worried about. :slight_smile:

I can’t think of any. Session data is stored on the server.

1 Like

That depends on the system, for a single server website this is true. If someone has high level access to the server, they basically have access to it all. On larger systems, setup across clusters this is seldom the case.

It is important to note, if your on shared hosting the default session storage can be a security issue, if the temp folder is shared across the accounts on the server.

Complexity wise, if this is added in a simplified form with just the link table and allowing one concurrent account login, it would be less than a hour work adding it to the Access class (i.e. when a login is completed, any old logins is invalidated).

The benefit of doing this, would be if additional functionality like those I mentioned is needed in the future, then it would be very easy to add them.

If you would like to manage without the link table, a solution is adding a new 8 char column in the user table. When a user login, you generate a random value that is inserted into this column. This value is also stored in the session together with the user id, and used on future random login validation checks similar to as I mentioned in last post.

I would not do that, for two reasons.

  1. The hash will require more storage place than the two other solutions provided.
  2. If the session storage is breached, it would allow collection of the hashed passwords that can be attempted for brute force offline. As mentioned above, depending on the system and servers included, that someone is able to breach the session storage, does not necessarily mean they have access to the rest of the data/servers.

If a host allows others to read a site’s session data, then that site is already compromised (session hijacking), regardless if we put hashed passwords in there or not.

That could be true (depends on the session handler used).

However, if a session hijacking give you full access to the account, then the security approach for the website/system is wrong. In a case, where the password has to be entered to make changes, then storing the password hash in the session give the attacker more information.

On a side note, luckily web hosts that do this is not that common anymore, it is usually only smaller companies that tries to handle web hosting for their customers, or who try to start a hosting company that does it. It is not like it was 10-15 years ago, when you could see this even at the larger hosts.

1 Like

Yes - and if the same data is stored in multiple places it will be easier to extract since it can be retrieved from whichever place is easier to access.

Duplicating data like this for no reason whatever is certainly not going to improve security - at best it is pointless to store the password hash in the session and it allows someone to steal the hashes more quickly by just grabbing the session data rather than having to read it from the database.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.