PHP
  Home arrow PHP arrow Creating a Secure PHP Login Script
Dev Articles Forums 
ADO.NET  
Apache  
ASP  
ASP.NET  
C#  
C++  
ColdFusion  
COM/COM+  
Delphi-Kylix  
Design Usability  
Development Cycles  
DHTML  
Embedded Tools  
Flash  
Graphic Design  
HTML  
IIS  
Interviews  
Java  
JavaScript  
MySQL  
Oracle  
Photoshop  
PHP  
Reviews  
Ruby-on-Rails  
SQL  
SQL Server  
Style Sheets  
VB.Net  
Visual Basic  
Web Authoring  
Web Services  
Web Standards  
XML  
Mobile Linux 
App Generation ROI 
IBM® developerWorks 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us Get Paid 
Request Media Kit
Contact Us 
Site Map 
Privacy Policy 
Support 
 USERNAME
 
 PASSWORD
 
 
  >>> SIGN UP!  
  Lost Password? 
PHP

Creating a Secure PHP Login Script
By: Martin Tsachev
  • Search For More Articles!
  • Disclaimer
  • Author Terms
  • Rating: 4 stars4 stars4 stars4 stars4 stars / 63
    2003-03-25

    Table of Contents:

    Rate this Article: Poor Best 
      ADD THIS ARTICLE TO:
      Del.ici.ous Digg
      Blink Simpy
      Google Spurl
      Y! MyWeb Furl
    Email Me Similar Content When Posted
    Add Developer Shed Article Feed To Your Site
    Email Article To Friend
    Print Version Of Article
    PDF Version Of Article
     
     
    ADVERTISEMENT


    In this article Martin explains how to create a secure PHP login script that will allow safe authentication. Features remember-me function using cookies, validates logins on each request to prevent session stealing.

    How Does This Work

    This is a short explanation why I have chosen these authentication methods.

    Users with shell access to the web server can scan valid session id's if the default /tmp directory is used to store the session data.

    The protection against this kind of attack is the IP check.

    Somebody who has a site (on a shared host with you) can generate valid session for your site.

    This is why the checkSession method is used and the session id is recorded in the database.

    Somebody may sniff network traffic and catch the cookie.

    The IP check should eliminate this problem too.

    Preparation

    You need first to decide what information to store about members, the examples provided will assume almost nothing to make it easier to read.

    I will use the PHP 4.1 super global arrays like $_SESSION, $_GET, etc. If you want to make it work on an earlier version of PHP you will have to substitute these with $GLOBALS['HTTP_SESSION_VARS'].

    Database Schema

    This is only an example bare structure suitable for online administration, if you want to have registered members you should add more columns.

    The schema is somewhat MySQL specific, I have yet to use another database other than MySQL and PostgreSQL but if you are using PostgreSQL you can convert the schema with the example script provided in my article Converting a database schema from MySQL to PostgreSQL.

    CREATE TABLE member (
      id int NOT NULL auto_increment,
      username varchar(20) NOT NULL default '',
      password char(32) binary NOT NULL default '',
      cookie char(32) binary NOT NULL default '',
      session char(32) binary NOT NULL default '',
      ip varchar(15) binary NOT NULL default '',
      PRIMARY KEY  (id),
      UNIQUE KEY username (username)
    );

    The password and cookie fields are md5 hashes which are always 32 octets long. Cookie is the cookie value that is sent to the user if he/she requests to be remembered, session and ip are respectively the session id and the current IP of the visitor.

    Connecting to the Database

    function &db_connect() {
     require_once 'DB.php';
     PEAR::setErrorHandling(PEAR_ERROR_DIE);
     $db_host = 'localhost';
     $db_user = 'shaggy';
     $db_pass = 'password';
     $db_name = 'shaggy';
     $dsn = "mysql://$db_user:$db_pass@unix+$db_host/$db_name";
     $db = DB::connect($dsn);
     $db->setFetchMode(DB_FETCHMODE_OBJECT);
     return $db;
    }

    This function connects to the database returning a pointer to a PEAR database object.

    Session Variables

    To ease access to the current user's information we register it as session variables but to prevent error messages and set some defaults we use the following function.

    function session_defaults() {
     $_SESSION['logged'] = false;
     $_SESSION['uid'] = 0;
     $_SESSION['username'] = '';
     $_SESSION['cookie'] = 0;
     $_SESSION['remember'] = false;
    }

    ... with a check like:

    if (!isset($_SESSION['uid']) ) {
     session_defaults();
    }

    to set the defaults. Of course session_start must be called before that.

    To the Core of the Script

    To allow easier integration with other scripts and make things more modular the core script is an object with very simple interface.

    class User {
     
    var $db = null; // PEAR::DB pointer
     var $failed = false; // failed login attempt
     var $date; // current date GMT
     var $id = 0; // the current user's id
     
    function User(&$db) {
      $this->db = $db;
      $this->date = $GLOBALS['date'];
      i
    f ($_SESSION['logged']) {
       $this->_checkSession();
      } elseif ( isset($_COOKIE['mtwebLogin']) ) {
       $this->_checkRemembered($_COOKIE['mtwebLogin']);
      }
     }

    This is the class definition and the constructor of the object. OK it's not perfectly modular but a date isn't much of a problem. It is invoked like:

    $date = gmdate("'Y-m-d'");
    $db = db_connect();
    $user = new User($db);

    Now to clear the code purpose, we check if the user is logged in. If he/she is then we check the session (remember it is a secure script), if not and a cookie named just for example mtwebLogin is checked - this is to let remembered visitors be recognized.

    Logging in Users

    To allow users to login you should build a web form, after validation of the form you can check if the user credentials are right with $user->_checkLogin('username', 'password', remember). Username and password should not be constants of course, remember is a boolean flag which if set will send a cookie to the visitor to allow later automatic logins.

     function _checkLogin($username, $password, $remember) {
      $username = $this->db->quote($username);
      $password = $this->db->quote(md5($password));
     
    $sql = "SELECT * FROM member WHERE " .
       "username = $username AND " .
       "password = $password";
      
    $result = $this->db->getRow($sql);
      
    if ( is_object($result) ) {
       $this->_setSession($result, $remember);
       return true;
      } else {
       $this->failed = true;
       $this->_logout();
       return false;
      }
     }

    The function definition should be placed inside the User class definition as all code that follows. The function uses PEAR::DB's quote method to ensure that data that will be passed to the database is safely escaped. I've used PHP's md5 function rather than MySQL's because other databases may not have that.

    The WHERE statement is optimized (the order of checks) because username is defined as UNIQUE.

    No checks for a DB_Error object are needed because of the default error mode set above. If there is a match in the database $result will be an object, so set our session variables and return true (successful login). Otherwise set the failed property to true (checked to decide whether to display a login failed page or not) and do a logout of the visitor.

    The logout method just executes session_defaults().

    Setting the Session

    function _setSession(&$values, $remember, $init = true) {
       $this->id = $values->id;
       $_SESSION['uid'] = $this->id;
       $_SESSION['username'] = htmlspecialchars($values->username);
       $_SESSION['cookie'] = $values->cookie;
       $_SESSION['logged'] = true;
       if ($remember) {
          $this->updateCookie($values->cookie, true);
       }
       if ($init) {
          $session = $this->db->quote(session_id());
          $ip = $this->db->quote($_SERVER['REMOTE_ADDR']);

          $sql = "UPDATE member SET session = $session, ip = $ip WHERE " .
             "id = $this->id";
          $this->db->query($sql);
       }
    }

    This method sets the session variables and if requested sends the cookie for a persistent login, there is also a parameter which determines if this is an initial login (via the login form/via cookies) or a subsequent session check.

    Persistent Logins

    If the visitor requested a cookie will be send to allow skipping the login procedure on each visit to the site. The following two methods are used to handle this situation.

    function updateCookie($cookie, $save) {
       $_SESSION['cookie'] = $cookie;
       if ($save) {
          $cookie = serialize(array($_SESSION['username'], $cookie) );
          set_cookie('mtwebLogin', $cookie, time() + 31104000, '/directory/');
       }
    }

    Checking Persistent Login Credentials

    If the user has chosen to let the script remember him/her then a cookie is saved, which is checked via the following method.

    function _checkRemembered($cookie) {
     list($username, $cookie) = @unserialize($cookie);
     if (!$username or !$cookie) return;
     
    $username = $this->db->quote($username);
     $cookie = $this->db->quote($cookie);
     $sql = "SELECT * FROM member WHERE " .
      "(username = $username) AND (cookie = $cookie)";
     $result = $this->db->getRow($sql);
     
    if (is_object($result) ) {
      $this->_setSession($result, true);
     }
    }

    This function should not trigger any error messages at all. To make things more secure a cookie value is saved in the cookie not the user password. This way one can request a password for areas which require even higher security.

    Ensuring Valid Session Data

    function _checkSession() {
     $username = $this->db->quote($_SESSION['username']);
     $cookie = $this->db->quote($_SESSION['cookie']);
     $session = $this->db->quote(session_id());
     $ip = $this->db->quote($_SERVER['REMOTE_ADDR']);
     
    $sql = "SELECT * FROM member WHERE " .
      "(username = $username) AND (cookie = $cookie) AND " .
      "(session = $session) AND (ip = $ip)";
     
    $result = $this->db->getRow($sql);
     
    if (is_object($result) ) {
      $this->_setSession($result, false, false);
     } else {
      $this->_logout();
     }
    }

    So this is the final part, we check if the cookie saved in the session is right, the session id and the IP address of the visitor. The call to setSession is with a parameter to let it know that this is not the first login to the system and thus not update the IP and session id which would be useless anyway.


    DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.

    More PHP Articles
    More By Martin Tsachev

     

    IBM® developerWorks developerWorks - FREE Tools!


    NEW! Build Web services with transport-level security using Rational Application Developer V7, Part 1: Build Web services and Web services clients

    Build secure Web services with transport-level security using IBM Rational Application Developer V7 and IBM WebSphere Application Server V6.1. Follow this three-part series for step-by-step instructions about how to develop Web services and clients, configure HTTP basic authentication, and configure HTTP over SSL (HTTPS). This first part of the series walks you through building a Web service for a simple calculator application. You generate and test two different types of Web services clients: a Java Platform, Enterprise Edition (Java EE) client and a stand-alone Java client. You also handle user-defined exceptions in Web services.
    FREE! Go There Now!


    NEW! Download the free Web Application Security eKit

    Discover how IBM Rational AppScan Standard Edition can help you detext vulnerabilities in your web applications in the Web Application Security eKit. IBM Rational AppScan is a leading suite of automated web application security solutions that scan and test for common Web application vulnerabilities. The new Web Application Security eKit provides you with valuable resources, including white papers, demos, and additional information on the benefits of testing your Web applications.
    FREE! Go There Now!


    NEW! Evaluate IBM Lotus Sametime Standard V8.0

    Visit IBM developerWorks to download a free trial of the latest release of IBM Lotus Sametime Standard V8.0. Lotus Sametime Standard V8.0 is a platform for unified communications and collaboration that combines security features with an extensible, open solution including integrated Voice over IP, geographic location awareness, mobile clients, and a robust Business Partner community offering telephony and video integration.
    FREE! Go There Now!


    NEW! Evaluate Rational Host Access Transformation Services (HATS) Toolkit V7.1

    Visit IBM developerWorks to download a free trial of the Rational Host Access Transformation Services (HATS) Toolkit. The HATS toolkit provides a set of plug-ins for the IBM Rational Software Delivery Platform to help you easily extend your legacy applications. HATS makes your 3270 and 5250 applications available as HTML through the most popular Web browsers, while converting your host screens to a Web look and feel and it also enables you to develop new Web, portal, and rich-client applications.
    FREE! Go There Now!


    NEW! Hello World: WebSphere Service Registry and Repository

    Manage, govern, and share services across your organization by using WebSphere Service Registry and Repository. Follow the hands-on exercises to learn how to navigate the Web interface to publish, find, reuse, and update services.
    FREE! Go There Now!


    NEW! IBM Enterprise Modernization Sandbox for System z

    IBM Enterprise Modernization solutions help organizations evolve core IT systems towards modern architectures and technologies—reducing the burden of maintenance and freeing up resources to develop new business requirements and capabilities. With the IBM Enterprise Modernization Sandbox for System z you can evaluate IBM Enterprise Modernization solutions focused on five key areas: Assets, Architectures, Skills, Processes and Infrastructures, and Investment. Each solution is based upon real customer experiences and offers a proven path to get you started with your modernization projects.
    FREE! Go There Now!


    NEW! Improve your build process with IBM Rational Build Forge, Part 2: Automate builds for a real-world Tomcat project

    Learn how Rational Build Forge can extend a simple compile and package build process by adding customization and deployment capability. Go from a manual method to automating: checking for code changes; getting the latest source; compiling and packaging; customizing; copying to and restarting a deployment server; and sending e-mail notification that a new version is available.
    FREE! Go There Now!


    NEW! Info 2.0: Harnessing the power of Web 2.0 and Enterprise Mashups

    Listen to this webcast to get an overview of Info 2.0 and a technical demo of how to quickly build an enterprise mashup. IBM's Info 2.0 technology leverages emerging Web 2.0 technologies such as mashups, feeds, AJAX, and JSON in order to simplify assembly of information using feeds and services. Come learn about the technical elements of Info 2.0 including the Feed Generation framework, Mashup Engine, and mashup assembly components. Learn how to pull information from databases, departmental information, and the Web to create mashups critical to your company’s success. We will also discuss best practices to help you get started.
    FREE! Go There Now!


    NEW! Webcast: Extreme transaction processing with WebSphere Extended Deployment

    In this webcast, you'll get an introduction to the eXtreme Transaction Processing (XTP) features of WebSphere Extended Deployment and the common architectural traits required by XTP applications. See how WebSphere Extended Deployment's ObjectGrid feature provides a state-of-the-art infrastructure for hosting XTP applications.
    FREE! Go There Now!


    NEW! Webcast: IBM Rational Build Forge - Beyond the Build

    The discipline of assembling and delivering software is maturing beyond standard developer-centric compile/test software builds. The end-to-end software development lifecycle is emerging as the new focus moves “Beyond the Build.” Join this on demand webcast to learn about methods for streamlining software delivery and key capabilities of the IBM Rational Build Forge framework for automating build and release management in environments of any size.
    FREE! Go There Now!



    All FREE IBM® developerWorks Tools!

    PHP ARTICLES

    - Making Usage Statistics in PHP
    - Installing PHP under Windows: Further Config...
    - File Version Management in PHP
    - Statistical View of Data in a Clustered Bar ...
    - Creating a Multi-File Upload Script in PHP
    - Executing Microsoft SQL Server Stored Proced...
    - Code 10x More Efficiently Using Data Access ...
    - A Few Tips for Speeding Up PHP Code
    - The Modular Web Page
    - Quick E-Commerce with PHP and PayPal
    - Regression Testing With JMeter
    - Building an Iterator with PHP
    - PHP Frontend to ImageMagick
    - Using PEAR's mimeDecode Module
    - Incoming Mail and PHP







    © 2003-2009 by Developer Shed. All rights reserved. DS Cluster 2 Hosted by Hostway
    For more Enterprise Application Development news, visit eWeek