Subscribe to Martyr2's Programming Underground        RSS Feed
-----

Really Simple PHP Login / Logout Script Example

Icon 11 Comments
One of the first scripts people new to PHP (or any server-side scripting really) create is a login and logout form for their site. Often times beginners get a little confused as to all the parts necessary to creating such a thing. So I thought it would be helpful if I showed a really basic and easy to use PHP login / logout script. The script uses an existing database connection to locate the user attempting to login, verifying their password and granting them credentials to view the rest of a site's pages. We will attempt to show how the password is looked up and compared, how to setup the HTML form to take in the username and password of the user and how to block access to pages for users who have not yet logged in. So let's get started!

There are four main parts to a login and logout script in PHP:

  • The first part is the actual login form which the user fills out and kicks off the login process.
  • The second is the login mechanism which will take the submitted username and password, find the user's password in the database and compare their password on file to the one they provided as part of the login.
  • The third part is then checking a user on each restricted page to see if they have successfully logged in.
  • Lastly we need a mechanism for destroying that user's state and basically causing them to go and login again. (aka logout)


Part 1 - Log In Form

Step 1 is about showing a basic login form. You don't have to create your form exactly like this, but you need a few key elements. You need the form tags which have a method of "post" and an action attribute that either submits to our processing page or, as I like to do it, the current page. You can have the form submit to itself by using $_SERVER["PHP_SELF"] or by using an empty value. You also need the two input fields which will collect the username and password for the user to login with. In addition to these, you should have a submit button which has a name specified with it. We will call ours "login-submit". Fitting isn't it? ;) This name is important because we will look for it when it comes time to determine if a login request has taken place. I have also included some PHP code that will look for an $error variable and if set will display the contents of the $error.

<!-- This form will post to current page and trigger our PHP script. -->
<form method="post" action="">
	<div class="login-body">
		<?php
			if (isset($error)) {
				echo "<div class='errormsg'>$error</div>";
			}
		?>
		<div class="form-row">
			<label for="emailaddress">Email:</label>
			<input type="text" name="emailaddress" id="emailaddress" placeholder="Email Address" maxlength="100">
		</div>
		<div class="form-row">
			<label for="pass">Password:</label>
			<input type="password" name="pass" id="pass" placeholder="Password" maxlength="100">
		</div>

		<div class="login-button-row">
			<input type="submit" name="login-submit" id="login-submit" value="Login" title="Login now">
		</div>
	</div>
</form>



As you can see from our code above, the form has a method of "post" and an empty action attribute (which will mean it will submit to the same page it is currently on). It also has two fields, "emailaddress" and "pass". It then has the submit button. The rest of the HTML is up to you to design and style as you see fit.

Part 2 - Login PHP Mechanism

So now we have a form which is submitting a username and password. We need some code that checks to see if some values were posted, use them to find our user in the database and compare the password we have on file to the one they are submitting. If successful, we know they are legit and can setup a session for them. Do keep in mind that this code should always be at the very top of your login.php page and above our form in part 1. We want to make our checks and setup the session first before the rest of the page is seen. If there is an error it will give us a chance to setup our $error variable and display it when we then go to show the form again.

Since the point of this article is talk about logging in and out and not exactly how to establish a database connection, we will assume you have a working database connection made. Here in the example I am using a custom PDO class (be sure to always use mysqli or PDO and never mysql_* style functions).

<?php
	// First start a session. This should be right at the top of your login page.
	session_start();

	// Check to see if this run of the script was caused by our login submit button being clicked.
	if (isset($_POST['login-submit'])) {

		// Also check that our email address and password were passed along. If not, jump
		// down to our error message about providing both pieces of information.
		if (isset($_POST['emailaddress']) && isset($_POST['pass'])) {
			$email = $_POST['emailaddress'];
			$pass = $_POST['pass'];


			// Connect to the database and select the user based on their provided email address.
			// Be sure to retrieve their password and any other information you want to save for the user session.
			$pdo = new Database();
			$pdo->query("SELECT id, email, password, name, level FROM db_users WHERE email = :emailaddr");
			$pdo->bind(':emailaddr', $email);
			$row = $pdo->single();


			// If the user record was found, compare the password on record to the one provided hashed as necessary.
			// If successful, now set up session variables for the user and store a flag to say they are authorized.
			// These values follow the user around the site and will be tested on each page.
			if (($row !== false) && ($pdo->rowCount() > 0)) {
				if ($row['password'] == hash('sha256', $pass)) {

					// is_auth is important here because we will test this to make sure they can view other pages
					// that are needing credentials.
					$_SESSION['is_auth'] = true;
					$_SESSION['user_level'] = $row['level'];
					$_SESSION['user_id'] = $row['id'];
					$_SESSION['name'] = $row['name'];

					// Once the sessions variables have been set, redirect them to the landing page / home page.
					header('location: home.php');
					exit;
				}
				else {
					$error = "Invalid email or password. Please try again.";
				}
			}
			else {
				$error = "Invalid email or password. Please try again.";
			}
		}
		else {
			$error = "Please enter an email and password to login.";
		}
	}
?>



Don't let this code discourage you. Most of it is inline comments so you know what is going on. We start by first launching our session_start(). We then check if someone submitted something through our login form. If so, the $_POST array is going to contain information about the submission including their username and password. We are going to test these values to make sure they exist and then we are going to use them in a database query which will lookup the user. Notice we just specify the email address in the where clause of our select statement. No need to pass the password too as this is a bit more secure. We just need to find the user in the database.

Once we find them we fetch out their password. We are going to then compare this password (which should be hashed in the database as a security precaution) and compare it to the hashed version of the password the user submitted in the login form. Here we are using a sha256 hash which is secure enough for something like this. If they match, we know we have the right user and they have successfully logged in. At this point we create some session variables. One of these variables is going to be a variable we will check for on each restricted page to know if they are logged in. If this value doesn't exist, then we know they never came through the login page successfully. We can then safely send them back to login.php to go login. In our example we called this variable "is_auth" and set it to true. Once the session variables are created, we forward the user off to home.php (the landing page you want the user to go to after logging in).

Note: At the time of setting session variables can also be used to send a login timestamp to the database to give a "last login" value. Might be nice if you wish to know when someone has logged in last or restrict their session time.

If the user fails at any point in this process, we are going to be setting up an error message in that $error variable. The rest of the page will then load and that place where we print out $error in the form will trigger showing the user our error message and giving them another shot to login.

Parts 3 & 4 - Checking Authorization On Restricted Pages and Logging Out

At this point all those who have successfully logged in have a session variable which is following them in their session called "is_auth". At the top of each page we want to restrict, we simply have a little code that will look for this variable. If not found (or has been destroyed) we immediately send the user back to the login page to login. This code goes at the top of each restricted page. Better yet you can put this code into its own file and simply include it at the top of each page using an include() PHP statement (again at the very top of each page).


<?php

session_start();

// Test the session to see if is_auth flag was set (meaning they logged in successfully)
// If test fails, send the user to login.php and prevent rest of page being shown.
if (!isset($_SESSION["is_auth"])) {
	header("location: login.php");
	exit;
}
else if (isset($_REQUEST['logout']) && $_REQUEST['logout'] == "true") {
	// At any time we can logout by sending a "logout" value which will unset the "is_auth" flag.
	// We can also destroy the session if so desired.
	unset($_SESSION['is_auth']);
	session_destroy();

	// After logout, send them back to login.php
	header("location: login.php");
	exit;
}

?>



So again we start our session and check to see if that "is_auth" variable has been set. If not, we immediately forward them off to login.php. If they are logged in, this code is skipped right over and the rest of the page loads as normal.

We have also put in an else if statement here which will listen to any passing of a value called "logout". If this value is set and its value is "true" we are going to unset our session variable and destroy the session. This is going to make it so that further pages will now no longer see that session variable and redirect them. Effectively logging them out and preventing them from seeing any more restricted pages. Once the session has been destroyed, we send them back to login.php. This logout variable can be passed by a form or through a link or whatever.

Conclusion

So that is all there is to it. Look through the code, read the inline comments and when you feel you have everything put together just remove the verbosity and you will see that this code gets pretty small. I have used this code on many occasions and it has served me well. It should serve as a nice base for further feature expansion. Like including "remember me" functionality, allowing you to set additional information in the user's session (like we show with a user's level or the theme options they have set) and allow you to not only check if they are logged in but how they are logged in and if they can actually see various items on the page. All by checking the session variables. I hope this code serves you as well as it has for me and gets you started on the path to creating your own wonderful login and logout mechanisms.

Thanks for reading! :)

If you liked this article and want to read more then check out our official blog at http://www.coderslexicon.com. There you can get code, tutorials and more. Plus you can find our popular ebook titled "The Programmers Idea Book" which features 200 programming project ideas, how to solve them and developer tips.

11 Comments On This Entry

Page 1 of 1

Anarion Icon

19 May 2015 - 12:53 PM
Looks like I am on a lucky spree! Started re-learning PHP recently (haven't used it since version 3!) and I found this blog post just today while trying to search for some reading material. Very well written! Thank you :)
P.S. also reading "PHP Wants To Be Your Web Language Of The Future" series of posts on Coders Lexicon. Highly suggested for those who search online and are in doubt about learning PHP for web development due to negative comments found everywhere.
0

CTphpnwb Icon

19 May 2015 - 10:49 PM
A couple of suggestions.
First, I wouldn't do this:
			$email = $_POST['emailaddress'];
			$pass = $_POST['pass'];

I'd do:
			$pass = hash('sha256', $_POST['pass']);

and bind $_POST['emailaddress'] and $pass to the query. If the query comes up empty, report an error: invalid credentials.
Second, to my eye this:
					$_SESSION['is_auth'] = true;
					$_SESSION['user_level'] = $row['level'];
					$_SESSION['user_id'] = $row['id'];
					$_SESSION['name'] = $row['name'];


is like parallel arrays. I'd make a user object and assign these attributes to it. Then I'd only need one session variable.
1

Martyr2 Icon

20 May 2015 - 09:24 AM

CTphpnwb, on 19 May 2015 - 10:49 PM, said:

A couple of suggestions.
First, I wouldn't do this:
			$email = $_POST['emailaddress'];
			$pass = $_POST['pass'];

I'd do:
			$pass = hash('sha256', $_POST['pass']);

and bind $_POST['emailaddress'] and $pass to the query. If the query comes up empty, report an error: invalid credentials.
Second, to my eye this:
					$_SESSION['is_auth'] = true;
					$_SESSION['user_level'] = $row['level'];
					$_SESSION['user_id'] = $row['id'];
					$_SESSION['name'] = $row['name'];


is like parallel arrays. I'd make a user object and assign these attributes to it. Then I'd only need one session variable.


Your first suggestion makes absolutely no difference and is purely preference. You just hash early and then put in the query where I hash during binding. Either way works and are "functionally" the same. As for your second comment... huh? That is not like parallel arrays, you are using the same array. You are just moving data into the session. Now sure you could create a single user object and then set properties, but my way actually might be more efficient in the fact that I don't have to define a class nor instantiate it. I also don't have to watch for any potential serialization issues that may crop up (even though rare). If I have a slew of preferences then for sure a class may make more sense. But for a few session variables the concept I presented actually simplifies things a bit.

But anyways your comments are duly noted. One might consider them if you were to expand on what I presented here. :)
1

CTphpnwb Icon

20 May 2015 - 07:01 PM
Sorry, I'll try to be more descriptive.

First, by hashing early you allow the query to carry more of the logic, so you can shorten your code.

Second, I wasn't trying to say that they were parallel arrays, only that like parallel arrays, you need to keep track of them from one page to the next. It's easier to let a class encapsulate it all.

While the code below has more lines, it's easier to reuse the Users class, and so the benefits of an OOP approach become more apparent as the project grows.
<?php
	// First start a session. This should be right at the top of your login page.
	session_start();

	// Check to see if this run of the script was caused by our login submit button being clicked.
	if (isset($_POST['login-submit'])) {

		// Also check that our email address and password were passed along. If not, jump
		// down to our error message about providing both pieces of information.
		if (isset($_POST['emailaddress']) && isset($_POST['pass'])) {
			$pass = hash('sha256', $_POST['pass']);
			
			// Connect to the database and select the user based on their provided email address.
			// Be sure to retrieve their password and any other information you want to save for the user session.
			$pdo = new Database();
			$pdo->query("SELECT id, email, password, name, level FROM db_users WHERE email = :emailaddr and passwored = :pass");
			$pdo->bind(':emailaddr', $_POST['emailaddress']);
			$pdo->bind(':emailaddr', $email);

// Haven't seen single() before and couldn't find it online. Are you sure you wouldn't use execute() and fetch()?
			$row = $pdo->single();

			// Whether or not the user record was found, set up a session variables for the user.
			// This value follows the user around the site and will be tested on each page.
			$myUser = new Users($row);
			$_SESSION['myUser'] = $myUser;
			
			if($myUser->auth()) {
				header('location: home.php');
				exit;
			}
			$error = "Invalid email or password. Please try again.";

		} else {
			$error = "Please enter an email and password to login.";
		}
	}
	
class Users {
	protected $authorized;
	protected $level;
	protected $id
	protected $name;
	
	public function __construct($userInfo)
	{
		if(is_array($userInfo) && isset($userInfo['level']))
		{
			$this->authorized = true; 
			$this->level = $userInfo['level']; 
			$this->id = $userInfo['id'];
			$this->name = $userInfo['name'];
		} else {
			$this->authorized = false; 
			$this->level = -1; 
			$this->id = -1;
			$this->name = '';
		}
	}
	
	public function auth()
	{
		return $this->authorized;
	}
	
	public function userLevel()
	{
		if($this->authorized) return $this->level;
		return false;
	}
}
?>


Note to any interested beginners: This code is untested! It's just an example of something that can work after some debugging!
0

Martyr2 Icon

20 May 2015 - 08:28 PM

CTphpnwb, on 20 May 2015 - 07:01 PM, said:

Sorry, I'll try to be more descriptive.

First, by hashing early you allow the query to carry more of the logic, so you can shorten your code.

Second, I wasn't trying to say that they were parallel arrays, only that like parallel arrays, you need to keep track of them from one page to the next. It's easier to let a class encapsulate it all.



I am not quite sure what you mean that the query will care more of the logic, it isn't carrying more of anything. Again, you have just chosen to hash at a different time. Either way the query still sees the same thing.

As for keeping track from one page to another, you would still have to keep track of properties of the object. Whether you stuff it in a variable of the session or the property of a class it again doesn't really give you any advantages "at this simple level". Your comment about the project growth is definitely valid. If the project gets bigger and you indeed need to keep track of more user data (like a custom theme options etc) by all means go the class route. But for simple "Are they allowed to see this or not?" the object is a bit overkill.

As for your comment in the code about $pdo->single(), as explained in the article I am using a custom class called "Database" which is handling my binding and fetching for me. Single is equivalent to a one row fetch.

:)
0

Martyr2 Icon

20 May 2015 - 08:32 PM

Martyr2, on 20 May 2015 - 08:28 PM, said:

CTphpnwb, on 20 May 2015 - 07:01 PM, said:

Sorry, I'll try to be more descriptive.

First, by hashing early you allow the query to carry more of the logic, so you can shorten your code.

Second, I wasn't trying to say that they were parallel arrays, only that like parallel arrays, you need to keep track of them from one page to the next. It's easier to let a class encapsulate it all.



I am not quite sure what you mean that the query will care more of the logic, it isn't carrying more of anything. Again, you have just chosen to hash at a different time. Either way the query still sees the same thing.

As for keeping track from one page to another, you would still have to keep track of properties of the object. Whether you stuff it in a variable of the session or the property of a class it again doesn't really give you any advantages "at this simple level". Your comment about the project growth is definitely valid. If the project gets bigger and you indeed need to keep track of more user data (like a custom theme options etc) by all means go the class route. But for simple "Are they allowed to see this or not?" the object is a bit overkill.

As for your comment in the code about $pdo->single(), as explained in the article I am using a custom class called "Database" which is handling my binding and fetching for me. Single is equivalent to a one row fetch.

:)


Actually I do see what you are saying because you are passing along the pass in the query. But I am explicitly against this approach as I see it is as unneeded. I don't want to be carrying along password info (hashed or not) via the query. We don't want any password data to be going back and forth any more than needed. At least that is how I look at it.
0

CTphpnwb Icon

21 May 2015 - 07:01 AM
Ah, I missed $pdo = new Database();!
Yes, for this example the class is a bit overkill, but who writes this much without expecting to continue? As for the password hash, it's going to be sent back from the sql server if you don't use it as part of the query, so I don't see a problem sending it to the server instead. The same data is traveling along the same connection.
0

CTphpnwb Icon

21 May 2015 - 08:11 AM
Actually, I prefer sending it to the server instead of having the server send it back! If you send it to the server and it's intercepted by a hacker, unless they take additional steps they're not completely sure it's correct! The user may have mistyped the password. What the server sends back will always be correct!
0

atraub Icon

21 May 2015 - 07:20 PM

Quote

...What the server sends back will always be correct!

And encrypted...
0

atraub Icon

21 May 2015 - 07:27 PM
Also bear in mind, the password is never sent to the client machine - it's still handled on the server.
0

CTphpnwb Icon

21 May 2015 - 08:02 PM
No, the password is hashed (not encrypted) either way here. What we're talking about is the difference between the SQL server sending the hash back to PHP to be compared against a hash of the password supplied by the user or PHP sending the hash to the SQL server. My point is that since a hash needs to be sent in any case, it's better for PHP to send it than to receive it.
0
Page 1 of 1