PHP
  Home arrow PHP arrow Creating a Secure PHP Login Script
Dev Shed Forums 
Administration  
AJAX  
Apache  
BrainDump  
DHTML  
Flash  
Java  
JavaScript  
Multimedia  
MySQL  
Oracle  
Perl  
PHP  
Practices  
Python  
Reviews  
Security  
Style-Sheets  
Web Services  
XML  
Zend  
Zope  
Forums Sitemap 
IBM® developerWorks 
Sun Developer Network 
Dedicated Servers 
E-Commerce Hosting 
Linux Web Hosting 
Managed Hosting 
Small Business Hosting 
Moblin 
JMSL Numerical Library 
VPS Hosting 
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 / 761
    2004-04-12

    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! Best Practices: The Integrated Project and Portfolio Management Platform.

    Hear how IBM Rational Project and Portfolio Management integrated solutions help teams put the right tools and processes in place to maximize the effectiveness and efficiency of project teams and ensure that the business vision is being executed correctly. Learn how to automate and integrate requirements prioritization, top-down project planning, communications and controls, and methodology deployment to keep your scope, costs, and schedules under control. Tackle with an end-to-end approach the management of scope and scope changes, usage of methodology to control and empower project teams, and optimization of resources to align activity costs with the overall project plan.
    FREE! Go There Now!


    NEW! Applying lean thinking to the governance of software development

    Effective governance for lean development isn’t about command and control. Instead, the focus is on enabling the right behaviors and practices through collaborative and supportive techniques. Hear from Scott Ambler on how it is far more effective to motivate people to do the right thing than it is to force them to do so. Learn how to form a lightweight, collaboration-based framework that reflects the realities of modern IT organizations.
    FREE! Go There Now!


    NEW! Maintaining QoS and Process Integrity in an SOA Environment

    This webcast outlines the best practices that must be instituted to gain the maximum benefit from SOA while maintaining high quality of service. Whether you are deploying new applications or managing and monitoring your existing infrastructure, learn how you can ensure high quality of services with SOA based solutions from IBM. All registrants who attend this live Web Seminar will receive complimentary access to a white paper titled “Maintaining QoS in an SOA Environment”.
    FREE! Go There Now!


    IBM DB2 Deep Compression ROI Tool

    The IBM DB2 Deep Compression ROI tool is designed for DBA’s and IT management personnel to perform a clinical analysis of the cost savings gained from the Storage Optimization feature of DB2 9 for Linux, UNIX and Windows. The feature, also known as Deep Compression, compresses data that lies within a database by up to 80% at times.
    FREE! Go There Now!


    NEW! Trial download: IBM Informix Dynamic Server Express Edition V11.0

    Informix Dynamic Server (IDS) Express Edition offers outstanding online transaction processing (OLTP) database performance, while helping to simplify and automate many of the tasks associated with deploying databases for small business applications. IDS 11 further extends the ease of management and applications integration with the Admin API and Scheduler, high availability with Continuous Log Restore for backup server recovery in case of a primary server failure, and column level encryption to protect personal and company private data.
    FREE! Go There Now!


    NEW! Cook up Web sites fast with CakePHP, Part 4: Use CakePHP's Session and Request Handler components

    CakePHP is a stable production-ready, rapid-development aid for building Web sites in PHP. This "Cook up Web sites fast with CakePHP" series shows you how to build an online product catalog using CakePHP.
    FREE! Go There Now!


    NEW! Run your first CICS application on a PC using TXSeries for Windows

    Learn the basics of the IBM Customer Information Control System (CICS). With a hands-on exercise, learn how to get your first CICS application up and running on your desktop using TXSeries V6.1 for Windows. The tutorial shows you how to download and install a free trial version of TXSeries V6.1.
    FREE! Go There Now!


    NEW! Evaluate WebSphere Extended Deployment Compute Grid V6.1

    Visit IBM developerWorks to download a free trial version of WebSphere Extended Deployment Compute Grid, which lets you schedule, execute, and monitor batch jobs. Because online transaction processing and batch jobs execute simultaneously on the same server resources, you can avoid costly duplication of resources. Compute Grid supports job types of Java transactional batch, compute-intensive and a new type called "native execution", which enables non-Java workloads to run on distributed end points.
    FREE! Go There Now!


    Role of Integrated Requirements Management in Software Delivery

    As organizations integrate software into every aspect of business, they are constantly pressured to deliver faster, better, and cheaper results. Unfortunately, a “dis-integrated” software delivery approach reduces returns while increasing costs. This IBM Rational White Paper shows how Integrated Requirements Management aligns organizations around maximizing value and keeping pace with change.
    FREE! Go There Now!


    NEW! Rational Talks to You: Grady Booch on Architecture

    Join this Rational Talks to You teleconference on November 29 at 1:00 pm ET to participate in an interactive discusssion with Grady Booch around architecture and reuse. Get your questions answered!
    FREE! Go There Now!



    All FREE IBM® developerWorks Tools!

       

    PHP ARTICLES

    - Paginating Database Records with the Code Ig...
    - HTTP Headers in Web Development
    - Project Management: Administration
    - Building a Database-Driven Application with ...
    - User Authentication for a Project Management...
    - Introduction to the CodeIgniter PHP Framework
    - Adding Users for a Project Management Applic...
    - Migrating Class Code for a MIME Email to PHP...
    - Login and Logout Authentication for a Projec...
    - Composing Messages in HTML for MIME Email wi...
    - Project Management: Authentication
    - A Better Way to Determine MIME Types for MIM...
    - Project Management Overview
    - Handling Attachments in MIME Email with PHP
    - Completing the Project Management Application





    © 2003-2008 by Developer Shed. All rights reserved. DS Cluster 5 hosted by Hostway