Creating a Secure Login Using Sessions and Cookies in PHP

Creating a secure login using sessions and cookies in PHP

There is no such thing as secure cookie UNLESS it's transmitted over SSL only. It can be mitigated some when using a persistent non-session cookie (like remember me), by doing exactly what you're doing, but not in the same way you're thinking of doing it.

You can indeed store server variables such as the user-agent, the ip address and so forth (and even JavaScript variables), but they are only good for validating that the persistent cookie data matches the client's new connection. The ip address isn't a good idea except when you know that the client (like you only) isn't going to change on every page load (a la AOL).

Modern web browsers and 3rd party services like LastPass can store login credentials that only require a key press (and sometimes not even that) to send the data to the login form. Persistent cookies are only good for those people who refuse to use what's available otherwise. In the end, persistent, non-session cookies are not really required anymore.

Safe Login using Sessions & Cookies

No, this is not secure!

I can see two major issues with the way you're doing this:

  1. The reason we don't store passwords in the database is so that in the event of a database compromise, an attacker can't gain access to the accounts on our server... but this is exactly what you're doing with your loginhash. If I compromise your database, I can log in as any user with an active cookie for the next 14 days just by creating a cookie with the username and the loginhash from the database.

    You can rectify this by generating your random key, storing that in a cookie, then running it through a hashing function like bcrypt and storing the digest in your database. This way, even with a full dump of your database, a user cannot simply log into your site as any other user. You will, of course, have to change the comparison operator so that it compares the digest instead of the raw.

  2. The str_shuffle function is not cryptographically secure. It uses rand internally which is fairly predictable and would make this hash a lot easier attack vector than your passwords. Instead, use the random entropy on your OS (URANDOM refers to Unblocking Random, it's not as secure as RANDOM but as your system will be doing a lot of processing -- and thus generating entropy -- it will suffice) like this:

Adapted from this password reset class:

function generateRandomBase64String($length = 32)
{
if (!defined('MCRYPT_DEV_URANDOM')) die('The MCRYPT_DEV_URANDOM source is required (PHP 5.3).');
$binaryLength = (int)($length * 3 / 4 + 1);
$randomBinaryString = mcrypt_create_iv($binaryLength, MCRYPT_DEV_URANDOM);
$randomBase64String = base64_encode($randomBinaryString);
return substr($randomBase64String, 0, $length);
}

Now, we need to make cookie jacking much more difficult

This article explains how Drupal handles login tokens, storing a username, loginhash and series_id together every time a user logs in with credentials instead of using a cookie. The loginhash and series_id can be generated using the aforementioned random function.

When a user logs in, the database is checked. If the username, loginhash and series_id are all present, the user is logged in and a new cookie is generated with the same username and series_id but a new loginhash. This means that each cookie can only be used once.

Additionally, users logged in this way are not able to do any compromising functions like requesting a password reset, withdrawing funds and viewing sensitive information without doing a 'proper' password login. This is to secure users who have has their cookie jacked.

If a login is detected where the series_id is present but the loginhash does not match, we wipe all valid cookies for this user and leave him a strongly worded message indicating that an invalid cookie was used to access his account.

On a side note re mysql_* functions

This seems like new code and not yet deployed. If it's not too late to turn back, consider switching your database driver to mysqli (documentation) which maintains a very similar procedural style to mysql but allows you to use bound parameters and other modern functions to improve data security.

PHP How to create a secure login system with cookies?

Since you're looking for the most secure solution (as of today's understanding), I'm professionally obligated to point to this blog post which explains the theory, and Gatekeeper which implements it.

Do's of Secure Login Systems

  • Don't roll it yourself, unless you're a security expert, or you have a security expert willing to audit your code afterwards (software security consulting isn't cheap)
  • Use bcrypt, scrypt, Argon2, or PBKDF2 to hash your passwords.
  • Use PHP's built-in session management system
  • Use HTTPS everywhere, with Hypertext Strict Transport Security
  • Use a Content-Security-Policy header to upgrade insecure requests
  • If you need to implement a "remember me" cookie, follow the guidelines in the above blog post:

    1. Generate two random tokens: a selector and an identifier
    2. Store the selector and identifier in a HTTP cookie, set to httpOnly=true and secure=true so it's only accessible over HTTPS (and hidden to JavaScript)
    3. Store the selector and a hash (SHA256 is okay here) of the identifier in the token table
    4. Authenticate the user, based on the token stored in their cookie, in constant-time.

Don'ts of Secure Login Systems

  • Don't store passwords in the database unhashed; this is never a good idea!
  • Don't encrypt or plainly encode passwords
  • Don't use a weak hash function (MD5, SHA1, etc.)
  • Don't use an insecure random number generator (you want to use random_bytes(), and if you're using PHP 5, random_compat).

Brief Security Analysis of the Above Advice

  • If you're using properly implemented HTTPS, you don't have to worry about attacks like FireSheep.
  • If you're using password_hash() for password storage, and password_verify() to authenticate against the stored hash, then you've accomplished defense-in-depth against an attacker dumping your entire database
  • For long-term authentication (cookie-based), you've separated the lookup (which is not possible to implement in constant-time, and is a random selector generated by a CSPRNG so you're unlikely to guess it) from the validation.
  • A hash of the long-term identifier is stored instead of just the identifier itself, as defense-in-depth against read-only database leaks
  • The validation is performed in constant-time, which makes it more robust against side-channel cryptanalysis (translation: nobody can use timing information to tease a valid long-term authentication token out of the database)

All of the the popular attack vectors (interception, hash cracking, defeating a weak random number generator) and some theoretical attack vectors (timing leaks) are, thus, eliminated. A little bit of good security engineering goes a long way.


Keep in mind that even security advice must carry an expiration date, so if you read this answer many years in the future some of it may be rendered obsolete. Don't get complacent, better ways are constantly being discovered.

PHP: User logged in sessions and cookies

First off, there is an important difference between a session and a cookie. When you use the $_SESSION[".."] you are creating a session (which lives on the server, compared to a cookie which lives on the client), even though the browser uses a cookie to keep track of the session id. To create a cookie you would use the setcookie() method.

That said, I would recommend you to read through this article which is a step-by-step guide on how to create a secure login script, with persistence using a cookie for a "Remember me"-feature. Describe how to do it in detail would be to extensive for an SO answer im afraid.

Side note:

To be able to write to the session, you might have to call session_start(); prior to getting or setting a session variable using $_SESSION[".."].

Securely creating and destroying login sessions in PHP

First of all you should read the Mozilla WebAppSec Security Coding Guideline - Session Management and OWASP A3-Broken Authentication and Session Management. You can configure PHP's session handler to meet these requirements.

The first flaw you should prevent is A9-Insufficient Transport Layer Protection. In short you do not want someone to hijack a session using a tool like Firesheep. This attack can be prevented by forcing the browser to only send the session id over https:

session.cookie_secure=1

You can prevent an attacker from obtaining the session id using XSS by setting the httponly flag:

session.cookie_httponly=1

You always want to use a cookie to store your session id. If the session id can be passed using a GET or POST variable then an attacker could use Session Fixation attack to hijack a session. Another way of thinking about this attack is that you don't want an attacker to create a session for another user:

session.use_cookies=1
session.use_only_cookies=1

Next you want to make sure you have atleast 128 bits of entropy from a CSPRNG. Under *nix systems you can use /dev/urandom:

session.entropy_file="/dev/urandom"
session.entropy_length=16

The session handler isn't everything. You still need to worry about Cross-Site Request Forgery attacks (aka CSRF or "Session Riding"), and Cross-Site Scripting (XSS). XSS can be used to defeat CSRF protection (even with http_only cookies!). Clickjacking can also be used by an attacker to perform unauthorized actions.

After you set these configuration options, just call session_start(). As for destroying the session call session_destroy() when the user logs out, its that simple!

PHP login session and cookie

It seems that you don't have a clear vision of sessions and cookies!

No body can change the session contents except your code (beside attacks). So you can store everything (reasonable) like user id or username that you need to access frequently. in cookies you must store some obfuscated information that you can recognize user later when he/she tries to access your page. so based on cookie content you can regenerate users session (ie. re-login user automatically). Just to note that user CAN change cookies content so it must not be something simple like user id for security reason.

I just give you a simple example, it's far from perfect but not so bad! you may need to tailor it to fit your scenario:

here you can create cookie content like this:

$salt = substr (md5($password), 0, 2);
$cookie = base64_encode ("$username:" . md5 ($password, $salt));
setcookie ('my-secret-cookie', $cookie);

and later to re-login user you do:

$cookie = $_COOKIE['my-secret-cookie'];
$content = base64_decode ($cookie);
list($username, $hashed_password) = explode (':', $hash);

// here you need to fetch real password from database based on username. ($password)
if (md5($password, substr(md5($password), 0, 2)) == $hashed_password) {
// you can consider use as logged in
// do whatever you want :)
}

UPDATE:

I wrote this article that covers this concept. Hope it helps.

What is a relatively secure way of using a login cookie?

I think I've found a clever solution!

Advantages of this (complicated?) script:

  • When the user successfully logs in
    with Remember Me checked, a login
    cookie is issued in addition to the
    standard session management
    cookie.[2]
  • The login cookie contains the user's username, a series identifier, and a token. The series and token are unguessable random numbers from a suitably large space. All three are stored together in a database table.
  • When a non-logged-in user visits the site and presents a login cookie, the username, series, and token are looked up in the database.
  • If the triplet is present, the user
    is considered authenticated. The used
    token is removed from the database. A
    new token is generated, stored in
    database with the username and the
    same series identifier, and a new
    login cookie containing all three is
    issued to the user.
  • If the username and series are
    present but the token does not match,
    a theft is assumed. The user receives
    a strongly worded warning and all of
    the user's remembered sessions are
    deleted.
  • If the username and series are not
    present, the login cookie is ignored.

I've made a table in the database with the following information:

    session | token | username | expire

The remember me cookie will have this setup:

    $value = "$session|$token|$userhash"; //Total length = 106
  • Session will be a string of 40 (sha1)
    characters.
  • Token will be a string of 32 (md5)
    characters.
  • Userhash in the cookie will be a
    string of 32 (md5 of username)
    characters.
  • Username in the database will be the
    normal username.
  • Expire will be now + 60 days.

The script:

if(isset($_SESSION['check']) || $_SESSION['check']){
//User is logged in
}else if(isset($_COOKIE['remember']) && strlen($_COOKIE['remember'])==106){
//THERE is a cookie, which is the right length 40session+32token+32user+2'|'
//Now lets go check it...
conncectdb(); //Sets connection
//How do I protect this script form harmful user input?
$plode = explode('|',$_COOKIE['remember']);
$session = mysql_real_escape_string($plode[0]);
$token = mysql_real_escape_string($plode[1]);
$userhash = mysql_real_escape_string($plode[2]);
$result = mysql_query(" SELECT user
FROM tokens
WHERE session = '$session'
AND token = '$token'
AND md5(user) = '$userhash';")
if(mysql_num_rows($result)==1){
//COOKIE is completely valid!
//Make a new cookie with the same session and another token.
$newusername = mysql_result($result,0,0);
$newsession = $session;
$newtoken = md5(uniqid(rand(), true));
$newuserhash = md5($username);
$value = "$newsession|$newtoken|$newuserhash";
$expire = time()+4184000;
setcookie('remember', $value, $expire, '/', 'www.example.com', isset($_SERVER["HTTPS"]), true);
mysql_query(" UPDATE tokens
SET token='$newtoken', expire='$expire'
WHERE session = '$session'
AND token = '$token'
AND md5(user)='$userhash';");
//Set-up the whole session (with user details from database) etc...
} else if(mysql_num_rows(mysql_query("SELECT user FROM tokens WHERE session = '$session' AND md5(user) = '$userhash';"))==1)){
//TOKEN is different, session is valid
//This user is probably under attack
//Put up a warning, and let the user re-validate (login)
//Remove the whole session (also the other sessions from this user?)
} else {
//Cookie expired in database? Unlikely...
//Invalid in what way?
}
} else {
//No cookie, rest of the script
}

Advantages of the script:

  • Multiple login. You can create new
    sessions for each computer you're on.
  • Cookie and database will stay clean.
    Active users renew there cookie every
    login.
  • The session check at the beginning
    ensures that the database will not
    get useless requests.
  • If an attacker steals a cookie, it
    gets a new token, but not a new
    session. So when the real user visits
    the website with the old(invalid)
    token but WITH a valid user-session
    combination the user gets a warning
    of the potential theft. After
    re-validating by logging in a new
    session is created and the session
    the attacker holds is invalid. The
    re-validating ensures the victim
    really is the victim, and not the
    attacker.

Reference: http://jaspan.com/improved_persistent_login_cookie_best_practice

How to make my simple php/html login secure using Sessions? i.e. Unable to 'back' into application when logged out

you need to register the new 'loggedIn' session variable when you retrieve the data from the database or you can also use your existing session variable ie username against the starting of each page instead of adding a new session variable to check the same....

// If result matched $myusername and $mypassword, table row must be 1 row
if($count==1){

// Register $myusername, $mypassword and redirect to file "index.php"
$_SESSION["username"] = $myusername;
$_SESSION["password"] = $mypassword;
$_SESSION['loggedIn'] = true;
header("location:../index.php");
}

Designing a secure auto login cookie system in PHP

The more secure you want this infamous cookie, the more trouble it's going to be for you. If your users should be particularly secure, you will have to go with the most troublesome approach.

You should only accept this cookie with https if you want to be as secure as possible. If the cookie is accepted over http, it can be sniffed and stolen.

I would recommend that the cookie have no user data at all (a token, as you suggested). This will, unfortunately, require another table. When a user logs in and chooses "keep login," create an entry in this table. The entry can be any meaningless value (such as md5(uniqid('', true));. This token can be unique in the DB and mapped to a user's ID.

When a user visits your website, you can check the value of that cookie and get the user it belongs to and log them in. At this point, you destroy the old token and create a new one. "Destroy" can mean many things. You can delete it from the DB entirely or have a flag that disables the token. You may want to allow the same token to be used multiple times in case the cookie is received but the authentication doesn't go through for some reason, but I think this is insecure. You may also want to store the timestamp of the token and only accept it if it's been some limited period of time (30 days for example).

As your friend points out, you can store other information such as user agent, IP address, etc., but these may change even with the same browser being used (especially with mobile) and if a user's persistent login is not accepted because of this, it could be jarring and inconvenient to them.

If you really don't want to create another table, then you will have to store some way to acquire the user's ID from the cookie value. This is less secure.



Related Topics



Leave a reply



Submit