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:
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 theusername
and theloginhash
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.The
str_shuffle
function is not cryptographically secure. It usesrand
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:
- Generate two random tokens: a
selector
and anidentifier
- Store the
selector
andidentifier
in a HTTP cookie, set to httpOnly=true and secure=true so it's only accessible over HTTPS (and hidden to JavaScript) - Store the
selector
and a hash (SHA256 is okay here) of theidentifier
in the token table - Authenticate the user, based on the token stored in their cookie, in constant-time.
- Generate two random tokens: a
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, andpassword_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
Session_Start() Creates New Session Every Refresh
Blank Spaces in Column Names with MySQL
Group Subarrays by One Column, Make Comma-Separated Values from Other Column Within Groups
Update Xampp from Maria Db 10.1 to 10.2
Quickest Way to Read First Line from File
Creating a Very Simple 1 Username/Password Login in PHP
How to Post Multiple <Input Type="Checkbox" /> as Array in PHP
Yii2 Global Filter/Behavior to Force User to Authenticate First
Multiple Auto Increment in MySQL
Check If Current User Is Administrator in Wordpress
Does PHP Close the File After the File Handler Is Garbage Collected
Displaying Multiple Lines of a File, Never Repeating
Have Trouble Installing PHPmyadmin on PHP7 Apache/2.4.7 (Ubuntu)
Printing a New Line in a CSV File Cell
Cannot Use Concatenation When Declaring Default Class Properties in PHP