PHP Programming/Building a secure user login system

From Wikibooks, open books for an open world
Jump to: navigation, search
Cross Site Scripting Attacks PHP Programming
Building a secure user login system

Many beginning PHP programmers set out to build a website that features a user login system but are unaware of the awaiting pitfalls. Below is a step-by-step guide through the necessary components of both a user authentication system and a user authorization system. The former is about determining whether users are who they say they are, while the latter is concerned with whether or not users are allowed to do what they are trying to do (e.g. gain access to a particular page, or execute a particular query).


There are two parts to the authentication process:

  1. The login form. The user is presented with some way of entering their credentials; the system checks these against a list of known users; if a match is found, the user is authenticated. This part of the system generally also initiates some way of remembering that a user is authenticated (such as by setting a cookie) so that this process doesn't have to be repeated for each request.
  2. The per-request check (for want of a better name!). This is the same as the second part of the login form process, with the user credentials being acquired from a source more convenient to the user—such as the cookie.

The code given below may need adjustment depending on the architecture of your scripts, whether object-oriented or procedural, having a single entry-point into your code or a dozen scripts each called separately. However the components of a login system are executed, their fundamentals are the same. Similarly, when a 'database' is referred to, it does not necessarily mean MySQL or any other RDBMS; user information could be stored in flat files, on an LDAP server, or in some other way.

The Login Form[edit]

This is the simplest part of the system, and the easiest place to start. Put simply: an HTML form is presented to the user, the user enters his credentials, the form contents are submitted to the next part of the login system for processing.

The user's credentials are generally a username and password, although others are possible (such as a nonce from a hardware token-generator). Many sites are now using the user's email address rather than a username. The advantages of this are that the e-mail addresses will be unique to one user, and it allows people to have consistent usernames since users with common usernames may not be able to get the same username everywhere they register.

The HTML for a basic login form could be something like this:
<form action="/login" method="post">
    <label for="email">Email address:</label>
    <input id="email" type="text" name="email" />
    <label for="password">Password:</label>
    <input id="password" type="password" name="password" />

Per-request Check[edit]

The users authenticity must be verified upon each HTTP request (i.e. for a page, image, or whatever). This is ostensibly as simple as seeing whether the relevant session variable is set:

The basic authentication check that needs to be run for each user request.
if (!isset($_SESSION['email_address']))
    // User is not logged in, so send user away.
// User is logged in; private code goes here.

This is sufficient in many ways, but it is vulnerable to a number of attacks. It relies utterly upon the session being tied to the right user. This is not a good thing, because sessions can be hijacked (the session key can be stolen by a third party) or fixed (a third party can force a user to use a session key that the third party knows). Read the sessions page of this Wikibook for more information about this and how to avoid it.

The basic authentication check, with additional guards against session hijacking or fixation.
$timeout = 60 * 30; // In seconds, i.e. 30 minutes.
$fingerprint = md5($_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT']);
if (    (isset($_SESSION['last_active']) && $_SESSION['last_active']<(time()-$timeout))
     || (isset($_SESSION['fingerprint']) && $_SESSION['fingerprint']!=$fingerprint)
     || isset($_GET['logout'])
    setcookie(session_name(), '', time()-3600, '/');
$_SESSION['last_active'] = time();
$_SESSION['fingerprint'] = $fingerprint;
// User authenticated at this point (i.e. $_SESSION['email_address'] can be trusted).

The $_SESSION['last_active'] and $_SESSION['fingerprint'] variables will also need to be set at the initial log in point (where the login form was processed) if they are to be used; simply insert the following lines above where the email_address variable is set:

The code used to check if the user is the same user that logged in.
$fingerprint             = md5($_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT']);
$_SESSION['last_active'] = time();
$_SESSION['fingerprint'] = $fingerprint;

One thing to remember when using browser fingerprints, however, is that, while they do add some security to your application, they are not a catchall by any means. Many ISPs offer Dynamic IP addresses, which are IP address that change at certain intervals. If this were to happen while a user is browsing your page, he would be kicked out of his account. Also, the code snippet that checks to make sure the browser is the same, can be modified via Firefox extensions that modify the headers while requesting the page.

And that is how a secure user-authentication system is implemented in PHP5! There are a number of points that have been glossed over in the information above, and some that have been left out completely (e.g. how to only start a session when necessary). If you are implementing your own user login system and are trying to follow the advice given here, there are sure to be things that you will figure out as you go and will wish were explained here, so please be bold and edit this page to add anything that you feel is missing.



To do:

  • ACLs
  • User-, group-, role-based authorization.
  • Other stuff…
Cross Site Scripting Attacks PHP Programming
Building a secure user login system