PHP login

PHP login

Friday, September 7th, 2007 in PHP, Security

After searching the web for a php login script (and you can find tons of it), I declared myself a little bit unsatisfied. I was looking for something simple yet powerful and I found all sorts of login scripts (a lot of them not safe at all). By the way, as a warning, BEWARE OF THE LOGIN SCRIPTS THAT HAVE SOMETHING LIKE: $_SESSION['has_access'] = true; INTO THEIR CODE followed by the explanation:”and you simply check the pages using if($_SESSION['has_access'])“.They are not safe at all, but that’s another discussion.

So, what was i looking for? Well, as I said before, the login script should be small, reusable, simple to understand and implement, it should check the user on every page (and I mean CHECK like … strip search). Since the www offer was not good enough, I started implementing my own code.

In order to make it reusable, I thought about creating an object to do this task for me. So, what this php login script does it’s actually quite simple: for every secured page it is installed on, it checks using the data stored in session if the user credentials are ok and if they are it returns his/her details from the database.

The php login class has two main methods: a function called login_user() with two parameters, one for the password and one for the username and a function called check_user() which takes only one parameter under the form of an MD5 hash for the concatenated string made from the username and the password. Looks blurry? It’s actually quite simple.

In login_user, we start by verifying if the username and password combination inserted by our user is ok, and if it is, we return an array containing his username and password under the form of an MD5 hash and the date of his/her last login. We do that by querying our database for the given user-password combination.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function login_user( $user , $password )
{
	$sql = "SELECT * FROM users
		    WHERE MD5(username) = '".md5($user)."'
		    AND password = '".md5( $password )."'";
	$query = mysql_query( $sql );
	if( mysql_num_rows($query) > 0 )
	{
		$result = mysql_fetch_assoc( $query );
		$sql_last_login = "UPDATE users SET
					last_login = UNIX_TIMESTAMP()
					WHERE id_user = ".$result['id_user'];
		mysql_query($sql_last_login);
		return  array(
			'session_identifier'=>md5( $result[ 'username' ].$result[ 'password' ] ),
			'last_login'=>$result['last_login']
				);
	}
	else
		return false;
}

Some things you should know: passwords from the database are stored using MD5 hash in this example. The username is saved in plain text, but the verification into the database is made using a hashed version (no special purpose for that, I only wanted to skip the verifications).

Ok now, as you may notice, the functions verifies if the query returns something and if it does, it returns an array containing a sesion identifier and the date of the last login (you can show it to the user or use it for any other purpose). The session identifier is composed from the MD5 hashed concatenated strings of username and password column, giving us a single string to check our user.

The second function ( check_user() ) does exactly what its name says: it checks on every page that requires login if our user is entitled to see that page. It does that by verifying the session_identifier variable against the records in our database.

1
2
3
4
5
6
7
8
function check_user( $md5_sum )
{
	$sql = "SELECT * FROM users
		    WHERE MD5(CONCAT(username,password)) = '".$md5_sum."'";
	$query = mysql_query( $sql )or die( mysql_error() );
	$result = mysql_fetch_assoc( $query );
	return count( $result ) > 0 ? $result : false;
}

As you can see, in case our user is fake or something, the function returns false. That will be it. A sample usage can be found below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require_once("the_file_where_you_stored_the_class.php");
$login = new login();
 
if( isset( $_POST[ 'username' ] ) )
{
	$login_result = $login->login_user( $_POST['username'] , $_POST['password'] );
	if( $login_result )
	{
		$_SESSION[ 'user_identifier' ] = $login_result[ 'session_identifier' ];
		$_SESSION[ 'last_login' ] = $login_result[ 'last_login' ];
	}
	else
        {
		// you'll need to change this to point your error page
                header("Location:login_error_page.php");
        }
}
$user_details = $login->check_user($_SESSION['user_identifier']);

Above is a sample usage. One of these days i’ll upload the example files too (I don’t have one ready right now). Bottom line, to check if the user is entitled to see a secured page, you simply check the $user_details variable (ie. if(!user_details) die(‘no access man’) ).

Also, if you can make any suggestions, improvements or any other remarks, please do by commenting this article.

Was this useful? Show your support.

digg PHP login

17 comments

  1. Neil says:

    Hi Constantin
    I just found your blog via a link to the Mootools content. It’s great.
    However I am particularly interested in this PHP login post. I can find plenty of examples online but like you, I haven’t found them all that satisfactory or to be honest, easy to follow (I am a PHP newbie).
    This is a very useful post and it would be really great to see any example files you could put up.
    many thanks
    Neil

  2. Hi Neil,
    I’m happy you like the blog. About the login script, I’ll try to prepare a downloadable script sometimes next week and I’ll let you know when it’s done.

  3. Neil says:

    Thanks Constantin!

  4. Gigi Duru says:

    Cool, check mysql on EVERY pageview …. that’s a great way to kill your server ;-)

    You didn’t say what’s wrong with checking $_SESSION['some_variable'] every page …

  5. I don’t think a simple query can kill your server. Maybe we shouldn’t do any queries at all in all the pages. Go back to plain HTML so that we don’t kill servers. I ran a test on 1000 users table and the query took .02. I know, if you run an explain on the sql, it doesn’t look so good. That can be improved with a little imagination. What kills me is sarcasm. Instead of being smart ass, try share with us a better, safer solution. Now, that COULD help.

    About the $_SESSION['some_variable'] situation, I’ve seen this. It goes like this: you have a login page and the user does an sql injection with your form. User input verification is not properly done and it validates. $_SESSION['some variable'] gets from false to true. All subsequent pages verify if the session variable is true and if it is grants access. So you have access to all pages even though you didn’t had a user. I repeat, I’ve seen this on live websites.

  6. Gigi Duru says:

    Actually, try having a site with 6 million pageviews/day (100 req/sec) and you WILL notice how much overhead a simple query adds to it. I’m the main developer of http://www.eventshooters.com/ (austrian portal like hi5), we get 180 mil. pageviews/month and I actually know what I’m talking about.

    As for the “problem” with $_SESSION, it’s quite simple: prevent SQL injection (you should do that anyway, i can’t imagine writing something in php without thinking of potential sql injections to that script) and no issue with $_SESSION.

    And, yes, sometimes you shouldn’t do queries in your pages. We use memcached all-over the place.

    Of course, i guess for “look mom, i made a php site with 500 visitors/month” php development your solution works too … :)

  7. Gigi Duru says:

    Sorry about the sarcasm, I’m a bit tired lately.

    You wanted me to share a better solution: store the userid in $_SESSION (or 0 if guest), on all pages which require a valid user check the $_SESSION. And, of course, prevent sql injections by making sure to sanitize any input to your script, don’t use register_globals, try reading about various php security tips and tricks, etc.

    I’ve been programming for 12 years now, in a lot of languages, not only php and I can tell you from own experience that the syndrome “let’s find another (maybe clever) way to do something, totally different than everybody else is doing it” is very bad for business.
    I’ve seen and worked with a lot of beginner developers which find on google various tutorials and examples (such as the example login you have here), don’t think for themselves and just copy+paste the code in their scripts and then are surprised that they get bad performance, bad security or plain old bad code… this is bad for the community, so if you want to HELP the community and beginners, explain WHY the proven methodology works, don’t waste time finding ‘clever ways’ to re-invent the wheel.

    You said you’ve seen live sites where you could bypass login via some sql injection.. then you’ve witnessed the phenomenon i’m talking about: bad programming due to bad tutorials.
    It’s very common (unfortunately) in the PHP world and that’s why java/c#/c++ developers sometimes look down at php developers saying they’re just some script kiddies… Of course, java/c# worlds have their own ’script kiddies’ but since many of them are making enterprise/desktop apps not everybody can see the results of their work, while php is being used for a lot of sites so it’s easier to discover the bad written ones.

    Programming is a very difficult job and has nothing to do with the language in which you’re programming.

    p.s.:
    http://www.sitepoint.com/blogs/2004/03/03/notes-on-php-session-security/ more info on REAL security issues with session.

  8. I liked the one with “look mom” :) . At the beginning, I think all websites are 500 visitors/month and grow with time. Once a problem arise, you are forced to make changes. You can’t know from the beginning (unless encountered before) what kind of problems a website might have.
    Anyway, thanks for sharing the rest of the info. I’ve never worked on sites as big as the one you mentioned so many of the issues you encountered are somewhat new. At that traffic, I must agree with you about what you said earlier. Thanks again.

  9. Liliana says:

    hi thx for this useful example… but i dont know how u insert in ur table users a password…? lets say i want to create a new user with a defined password how can i do it? im new so forgive me if this is a stupid question

  10. You simply enter the hash: INSERT INTO users_table SET username=’username’, password=MD5(‘password’). This should do it.

  11. ndowie says:

    Gigi Duru said: “As for the “problem” with $_SESSION, it’s quite simple: prevent SQL injection (you should do that anyway, i can’t imagine writing something in php without thinking of potential sql injections to that script) and no issue with $_SESSION.”

    I’m agree with Gigi, I using $_SESSION till now, then I read this post, the better idea is combine its.

  12. jamarchi says:

    Hi, I just find this blog and is great

    I tested the scrip but i saw this error in the protected.php page

    Notice: Undefined index: logout in C:\wamp\www\php_login\php_login\protected.php on line 19

    Can you please help me?

    • jamarchi says:

      I didn´t change anything

    • This is not an error, it’s just a notice. you must have error_reporting set to all. No problem, one change needed, line 19 is this one:

      if($_GET['logout'])

      All you need to do is check if variable is set, so instead of the line above, put this:

      if( isset($_GET['logout']) )

      This should do it. Let me know.

  13. Harry says:

    Hi Harry here,
    I’m working with your script – Thanks, I like it.
    I’ve made an ‘add user.php’ which works.
    and replaced some of the inline php with includes,

    include(‘db_connect.php’);
    if ($_POST['username']) { $username=$_POST['username']; }
    if ($_POST['password']) { $password=$_POST['password']; }
    if ($_POST['confirm']) { $admin_confirm=$_POST['confirm']; }
    if ($username AND $password AND $admin_confirm) {
    if ($password == $admin_confirm){
    $sql =mysql_query(“INSERT INTO users SET username=’$username’, password=md5(‘$password’), last_login = UNIX_TIMESTAMP()”);
    if (!$sql) {echo mysql_error();}

    echo “signup sucessful. Please wait. Redirecting you to our administration page now”; $head_info=”;
    }else{
    }
    }
    if ($admin_confirm AND $password){if($admin_confirm!=$password)
    {
    echo ‘Please Try Again’;
    $head_info=”;
    }
    } else {
    echo “Opps! You have missed some information”;
    $head_info=”;
    }
    ?>

    That bit goes ok…HOWEVER the ’session’ seems to slap me back
    in the face with a thorny fish where it comes to
    HIDING the login form once logged in.

    I’ve tried placing it in variables etc.. and using
    if (!$_POST) and MY WEB ADMIN == ‘false’ bla bla

    They all work the opposite way !! I’ve even tried putting the login form
    into index.php as an include. Sad indictment of my ongoing failures.

    Well this is just a thanks note – any insights would be appreciated.
    Regards Harry

  14. Harry says:

    Thanks Constantin, have I not done that here?

    if (!$sql) {echo mysql_error();}

    echo “signup sucessful. Please wait. Redirecting you to our administration page now”; $head_info=”;
    }else{
    }
    }
    if ($admin_confirm AND $password){if($admin_confirm!=$password)
    {
    echo ‘Please Try Again’;
    $head_info=”;
    }
    } else {
    echo “Opps! You have missed some information”;
    $head_info=”;

    Or do I need to do something like this BEFORE insert…

    if (!$_POST['username']) { $username_error=['$error']; }

    Regards Harry

Leave a comment