LOGIN & REGISTRATION Script Tutorial

Tutorials on PHP, databases and other aspects of web development. Before posting a question, check in here to see whether there's a tutorial that covers your problem.

Moderator: General Moderators

LOGIN & REGISTRATION Script Tutorial

Postby califdon » Sat Apr 14, 2012 6:37 pm

This tutorial was written and submitted by PHP Developers Network members Celauran and social_experiment.

Creating login systems can be a challenge for people who are new to PHP. For their benefit we decided to create an article showing some points to look at when creating a login system. These are common pitfalls identified by looking at previous questions from newbies who attempted to write login systems but got stuck somewhere along the way. This may cause some to continue using poorly crafted scripts, only to realize later (possibly after a cracking attempt) that the script wasn't properly secured.

Because this is aimed at less experienced users, a few things to point out before starting:

1. Any page that uses sessions has to have the session_start() function at the top (before any other output except the php opening tag) of that specific page.
Syntax: [ Download ] [ Hide ]
<?php
 session_start();
 // rest of code
?>


If you have 5 pages to protect, all 5 pages needs session_start() at the top.

2. When using mysql_ or mysqli_ functions, you need to make a connection to the database. Certain mysqli_ functions require the use of a resource returned by mysqli_connect(). Throughout this article the presence of a database connection is assumed.

Pitfall 1: Storing passwords

The short answer is don't. You don't store passwords anywhere. Ever. Why? If your database is ever compromised, then every user account is also compromised. A list of usernames, passwords, and possibly email addresses in the hands of an attacker can be disastrous. Still, for a user to access your site, you need to validate the credentials they provide at login against those they provided at registration. The solution, then, is to use hashes. Store a hash of the password in your database, hash the password provided at login, and compare the hashes. Because hashing algorithms are one-way, computing the hash of any given value is trivial, but working backwards from the hash to the original value is impossible.

Still, not all hashing algorithms are created equal. For example, many tutorials -- and indeed some published works -- advocate the use of the md5 hashing algorithm for creating password hashes. While this may once have been acceptable practice, this is no longer the case. More on that later. For now, to create a reasonably secure working hash, we'll create a salt, create a pepper, combine these with the user's password, and finally hash with something like the sha384 algorithm (minimum). Salts are user-specific and can be stored in the database, while peppers are site-wide and often reside in a file somewhere or as a string which has various characters and is at least 30 characters long.

One key point to remember here is that you aren't trying to protect against unauthorized logins to your site; you're trying to prevent passwords from being discovered in the event that your user database is compromised. To that end, you want to employ a slow hashing algorithm. Read why. Take 5 minutes to read that article. Go ahead, I'll wait. You can use bcrypt directly by calling the crypt() function with a suitable salt, or you can take advantage of PHPass which uses bcrypt when available and degrades gracefully where it isn't. They also have an excellent article on hashing and user authentication.

Example of how you can create a hashed value:
Syntax: [ Download ] [ Hide ]
<?php
 $hashedValue = hash('sha384', $salt.$pepper.$password);
 // returns a 96 character string which is stored in the database
?>


Alternately, using PHPass:
Syntax: [ Download ] [ Hide ]
$hasher = new PasswordHash(8, FALSE);
$hash   = $hasher->HashPassword($password);


It's worth noting that any given algorithm will produce hashes of the same length regardless of the length of the input. These will often be considerably longer than the length of the password being hashed. Be sure to check the length of the output of your algorithm of choice and ensure that the password field in your database is sufficiently long to store the entire hash.

Pitfall 2: Not escaping input

Quite a few example scripts portray a query against the database in the following manner:
Syntax: [ Download ] [ Hide ]
<?php
$username = $_POST['username'];
$email    = $_POST['email'];
$qry = "SELECT * FROM Table WHERE username='$username' and email='$email'";
?>


The $_POST values above are taken directly from the form, without any checking of any sort. Input should always be treated as if it is contaminated. Before using data in a database query, it needs to be validated and escaped.

You can use mysql_real_escape_string() for this purpose. MySQLi (which you really ought to be using) has a similar function. Better still, make use of prepared statements. Regardless of which method you ultimately choose, the value of properly escaping your data cannot be overstated. Relevant.

mysql_real_escape_string() accepts an argument that is to be escaped making it safe to use in a database query. The explanation from the PHP Manual on the function:
PHP Manual wrote:Escapes special characters in the unescaped_string , taking into account the current character set of the connection so that it is safe to place it in a mysql_query(). If binary data is to be inserted, this function must be used. mysql_real_escape_string() calls MySQL's library function mysql_real_escape_string, which prepends backslashes to the following characters: \x00, \n, \r, \, ', " and \x1a.
This function must always (with few exceptions) be used to make data safe before sending a query to MySQL.


Depending on personal requirements you should also check for empty fields, certain types of characters, certain types of data. Checking the data you receive is just as important as escaping it. Use existing php functions such as trim(), ctype functions, filters, or regular expressions to ensure that input is valid and of the type expected. User input is NEVER to be trusted.

Some scripts rely on the magic_quotes_gpc setting to determine whether or not to use mysql_real_escape_string(); though you could check for the existence of the value, it is wise to note that from PHP 5.3.0 the feature is deprecated (the function shouldn't be used anymore). The above code snippet can be amended as follows:

Syntax: [ Download ] [ Hide ]
<?php
// no checking of data; improve this by using existing or custom
// functions.
$username = mysql_real_escape_string($_POST['username']);
$email    = mysql_real_escape_string($_POST['email']);
$qry = "SELECT columnA, columnB FROM TableName WHERE username='$username' and email='$email'";
?>


Pitfall 3: Session vulnerabilities

Sessions are commonly used to distinguish authenticated users from the unauthenticated. Typically, in processing a login form, you'll see something like this
Syntax: [ Download ] [ Hide ]
<?php
if ($rows==1)
{
    header("location:/login_success.php");
}
else
{
    echo "Wrong Username or Password";
}
?>


Unfortunately, this leaves you vulnerable to session fixation (PDF) attacks. As captured session IDs are generally worthless unless the user is signed in, you can protect against this by regenerating the session ID upon successful login. Session data is only written after this new ID has been generated.

Syntax: [ Download ] [ Hide ]
<?php
if($rows==1)
{
    session_regenerate_id();
    $_SESSION['user_id']  = $user_id;
    $_SESSION['loggedIn'] = true;
    // close the session
    session_write_close();
    header("location:/login_success.php");
    exit();
}
else
{
    echo "Wrong Username or Password";
}
?>


To additionally protect against session hijacking, add some sort of signature to the session. A combination of user ID, User-Agent, and some random salt should suffice. So we update the above to include this signature.

Syntax: [ Download ] [ Hide ]
<?php
if($rows==1)
{
    session_regenerate_id();
    $_SESSION['user_id']   = $user_id;
    $_SESSION['loggedIn']  = true;
    $_SESSION['signature'] = md5($user_id . $_SERVER['HTTP_USER_AGENT'] . $salt);
    // close the session
    session_write_close();
    header("location:/login_success.php");
    exit();
}
else
{
    echo "Wrong Username or Password";
}
?>


Of course, any page in a protected area is going to check that the requesting user has been authenticated, often using code similar to this.

Syntax: [ Download ] [ Hide ]
<?php
// session_is_registered() is a function that was deprecated as
// of php 5.3.0 and should not be used; it's presence here is
// as an example of how <span style="font-style: italic">not</span> to register session variables.

if (!session_is_registered(myusername))
{
    header("location:mainlogin.php");
}
?>


Effectively myusername can be empty and the auth script would see this as "logged in". The variable is registered but a better option would be to fill it with something concrete to check against. This is where our signature comes into play. By ensuring that user ID is present, loggedIn is true, and recalculating the signature and ensuring it matches what's stored in session data, we can be reasonably certain we're dealing with a legitimate user.

Syntax: [ Download ] [ Hide ]
if (!isset($_SESSION['user_id']) || !isset($_SESSION['signature']) || !isset($_SESSION['loggedIn']) || $_SESSION['loggedIn'] != true || $_SESSION['signature'] != md5($_SESSION['user_id'] . $_SERVER['HTTP_USER_AGENT'] . $salt))
{
    session_destroy();
    header("Location: mainlogin.php");
    exit();
}


A note about database connections: We have included mysql_ examples in some of the above code snippets simply for the sake of familiarity. Since the release of PHP 5 way back in 2004, MySQLi has been the preferred method for working with MySQL databases and we strongly recommend moving away from the old mysql_ functions. An alternative to MySQLi, which affords the flexibility of using a DBMS other than MySQL without having to rewrite your code, is the Portable Database Object, PDO. In either of these cases, we feel it preferable to make use of prepared statements to protect against SQL injection.

Please note that the aim of this tutorial is simply to help users avoid some common pitfalls we've seen time and again. This is not the be all, end all of login tutorials, nor do we claim anything here is foolproof. We hope that this has been of use to those who don't know where to start when writing a login system. For those who are experienced in the matter please add any comments, critique or additional tips so the article can be as complete as possible.

The attached .zip file contains the following files:

logintutorial.pdf (this tutorial as a PDF for printing)
login_mysqli.php
login_pdo.php
register_mysqli.php
register_pdo.php
users.sql
phpass-03/PasswordHash.php
phpass-03/test.php
phpass-03/c/crypt_private.c
phpass-03/c/Makefile
Attachments
login.zip
This .zip file contains supporting example script files and a PDF of the above tutorial.
(150.49 KiB) Downloaded 2551 times
User avatar
califdon
Jack of Zircons
 
Posts: 4484
Joined: Thu Nov 09, 2006 9:30 pm
Location: California, USA

Re: LOGIN & REGISTRATION Script Tutorial

Postby El Guaipeca » Fri May 11, 2012 8:30 am

Dear friend,

Nice post! Last week I asked my php's teacher about mysqli, but she does not know it.

So, I have got some issue when made the verification to protected pages.

Your article mentioned about this example code:

Syntax: [ Download ] [ Hide ]
if (!isset($_SESSION['user_id']) || !isset($_SESSION['signature']) || !isset($_SESSION['loggedIn']) || $_SESSION['loggedIn'] != true || $_SESSION['signature'] != md5($_SESSION['user_id'] . $_SERVER['HTTP_USER_AGENT'] . $salt))
{
    session_destroy();
    header("Location: mainlogin.php");
    exit();
}
 


My question is about the logical operator "||". I can have this working only with "&&" logical operator.

Example:
Syntax: [ Download ] [ Hide ]
if (!isset($_SESSION['user_id']) && !isset($_SESSION['authenticated']) && $_SESSION['authenticated'] != true && $_SESSION['signature'] != md5($_SESSION['user_id'] . $_SERVER['HTTP_USER_AGENT'] . $salt))
{
    session_destroy();
    header("Location: mainlogin.php");
    exit();
}
 

Best regards and thanks a lot for a nice post.
El Guaipeca
Forum Newbie
 
Posts: 2
Joined: Thu May 10, 2012 9:11 pm

Re: LOGIN & REGISTRATION Script Tutorial

Postby social_experiment » Fri May 11, 2012 8:41 am

Yes you can have it working with && as well; my personal reason for opting to use || (OR) is because even if only one condition is 'incorrect' the authentication process has failed and a user has to be logged in. The snippet of code you have says that all conditions have to be met before authentication is invalid i.e user_id is not set AND authenticated is not set AND authenticated is not equal to true AND...(etc).

The idea is still the same but i would rather have the authentication fail as easily as possible so any attempt to break it has to be made as difficult as possible.

I'm glad you found the tutorial useful :)
User avatar
social_experiment
DevNet Master
 
Posts: 2774
Joined: Sun Feb 15, 2009 12:08 pm
Location: .za

Re: LOGIN & REGISTRATION Script Tutorial

Postby El Guaipeca » Fri May 11, 2012 9:36 am

Hello!
It seems I have some incorrect condition I think.

I've got the verify session script working only in this way:

<?php
if (!isset($_SESSION['user_id']) || ($_SESSION['authenticated']) != TRUE || !isset($_SESSION['signature']))
{
session_destroy();
header("Location: mainlogin.php");
exit();
}
?>

Thanks again for this useful article.
El Guaipeca
Forum Newbie
 
Posts: 2
Joined: Thu May 10, 2012 9:11 pm

Re: LOGIN & REGISTRATION Script Tutorial

Postby Live24x7 » Mon Jun 04, 2012 4:15 pm

Never knew md5 hashing algorithm could be so easily compromised - the reasons mentioned now make me feel wary of md5.
I thought they were an established standard for storing passwords.

I have used md5 on a live website of mine. Now what do i do for the existing list ?

Infact i am using md5 on a current under-development project - will change it.
But really don't know what to do about my already live website.

SESSION['signature'] is a great idea that i heard for the first time - will definitely try to use it.

About security issues like session fixation attacks, session hijacking etc mentioned above - thanks for the excellent link specially the pdf link
Live24x7
Forum Contributor
 
Posts: 194
Joined: Sat Nov 19, 2011 10:32 am

Re: LOGIN & REGISTRATION Script Tutorial

Postby social_experiment » Tue Jun 05, 2012 12:44 am

Live24x7 wrote:
> I have used md5 on a live website of mine. Now what do i do for the existing list
> ?
you can't do much for existing passwords (if this is what you are referring to), they will have to be re-entered by the user if you want them to enjoy the use of the new hash algorithm.
User avatar
social_experiment
DevNet Master
 
Posts: 2774
Joined: Sun Feb 15, 2009 12:08 pm
Location: .za

Re: LOGIN & REGISTRATION Script Tutorial

Postby califdon » Tue Jun 05, 2012 11:38 am

Disclaimer: I have limited experience with user login security issues. That said, I think the appropriate action to take for an existing site using MD5 hashing would depend on how critical the security issues are on that particular site. If it is non-critical (say, a game site), I wouldn't recommend changing it. If it involves financial or deeply personal data, you could add a new password column in your user database for the bcrypt hash, then announce a security improvement program to your users: they will be redirected to a page where they can set a new password for their account (which you would hash into the new column), but after some date, perhaps several months away, accounts not changed will be inactivated, requiring them to set the new password. In the meantime, your authentication process could first check to see if a new password exists and if so, use it to authenticate, otherwise use the old password, during the transition period. After some period of time, any remaining accounts without the new password could just be deleted, since they would likely be abandoned.
I don't make syntax errors. It's just that sometimes whoever wrote the programming language failed to anticipate my creative coding.
User avatar
califdon
Jack of Zircons
 
Posts: 4484
Joined: Thu Nov 09, 2006 9:30 pm
Location: California, USA

Re: LOGIN & REGISTRATION Script Tutorial

Postby Bill H » Tue Jun 05, 2012 11:43 am

Or you could do it in a more transparent fashion. Set a "flag" in the database to indicate whether or not the encryption has been changed. Use the flag to determing which method is current whenever they login. Users should be changing passwords periodically anyway, so encourage them to do so, and if md5 is still in use when they change their password change it at that time and reset the flag.
User avatar
Bill H
DevNet Resident
 
Posts: 1135
Joined: Sat Jun 01, 2002 10:16 am
Location: San Diego CA

Re: LOGIN & REGISTRATION Script Tutorial

Postby califdon » Tue Jun 05, 2012 12:28 pm

WHAT?? Users should be changing passwords periodically?? What a radical idea! (Come on, tell me the truth, Bill, do YOU change yours periodically?--I'll be honest, I only do it when I'm forced to.)
I don't make syntax errors. It's just that sometimes whoever wrote the programming language failed to anticipate my creative coding.
User avatar
califdon
Jack of Zircons
 
Posts: 4484
Joined: Thu Nov 09, 2006 9:30 pm
Location: California, USA

Re: LOGIN & REGISTRATION Script Tutorial

Postby Bill H » Wed Jun 06, 2012 11:02 am

I was speaking theoritically. You should hear the language I use when some furshluginner bank makes me change my password. It would make you proud to have been in the same Navy with the Submarine Service.
User avatar
Bill H
DevNet Resident
 
Posts: 1135
Joined: Sat Jun 01, 2002 10:16 am
Location: San Diego CA

Re: LOGIN & REGISTRATION Script Tutorial

Postby califdon » Wed Jun 06, 2012 11:18 am

Actually I AM proud to have been in the same Navy as you silent and deep guys! GO NAVY!
I don't make syntax errors. It's just that sometimes whoever wrote the programming language failed to anticipate my creative coding.
User avatar
califdon
Jack of Zircons
 
Posts: 4484
Joined: Thu Nov 09, 2006 9:30 pm
Location: California, USA

Re: LOGIN & REGISTRATION Script Tutorial

Postby amigothecoder » Wed Aug 15, 2012 4:05 am

Though still novice in php, i found this tutorial very usefull and it is opening a way for me to become a good php programmer. thanks a lot to the author
amigothecoder
Forum Newbie
 
Posts: 2
Joined: Wed Aug 15, 2012 3:16 am

Re: LOGIN & REGISTRATION Script Tutorial

Postby akhilesh1010 » Fri Aug 23, 2013 2:16 am

Very good tutorial for mysqli, this is my week point.
akhilesh1010
Forum Newbie
 
Posts: 15
Joined: Thu Aug 22, 2013 1:56 am

Re: LOGIN & REGISTRATION Script Tutorial

Postby SomeSHET » Wed Nov 13, 2013 5:05 pm

how you would log the user out using a "logout" link
or how you would say "Hello, USER!" but instead of user it uses the username that matches with the id created
SomeSHET
Forum Newbie
 
Posts: 2
Joined: Tue Nov 12, 2013 10:40 pm

Re: LOGIN & REGISTRATION Script Tutorial

Postby SomeSHET » Wed Nov 13, 2013 6:23 pm

i think i got it, but i just want to make sure... everything seems to be working fine. the logout page is what im not so sure of..

the logout link leads to "mysite.poo/logout/" page were the code below is in an index.php
Syntax: [ Download ] [ Hide ]
<?php
if (!isset($_SESSION['user_id']) || ($_SESSION['authenticated']) != TRUE || !isset($_SESSION['signature']))
{
        session_start();
    session_unset();
    session_destroy();
        header("Location: http://mysite.poo/Login/");
}
?>
 
SomeSHET
Forum Newbie
 
Posts: 2
Joined: Tue Nov 12, 2013 10:40 pm

Next

Return to Tutorials

Who is online

Users browsing this forum: No registered users and 3 guests