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
developerWorks - FREE Tools! |
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!
|
|
|
|
David Barnes, Lead Evangelist for IBM Emerging Internet Technologies will discuss aspects of Web 2.0 that bring value to corporations, academia, and government. He'll also discuss IBM's vision around Web 2.0, including the importance of remixability and consumability. The discussion will culminate with examples of various IBM Software Group solutions you can use to get ahead of the Web 2.0 adoption curve. FREE! Go There Now!
|
|
|
|
Attend this launch webcast with Scott Hebner, Vice President of IBM Rational Marketing and Strategy, for an overview of Rational’s new software offerings and resources to help modernize and accelerate software innovation on i on Power Systems – while ensuring past application investments are protected and continue to grow. Learn how these solutions are helping customers extend their core i5/OS solutions toward modern architectures such as SOA and web technologies to deliver business improvements that stand the test of time. FREE! Go There Now!
|
|
|
|
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!
|
|
|
|
Download a free trial version of IBM DB2 9.5 for Linux, UNIX, and Windows. DB2 9 is the result of a five-year development project that transformed traditional (static) database technology into an interactive data server that merges the high performance and ease of use of DB2 with the self-describing benefits of XML. FREE! Go There Now!
|
|
|
|
Download a free trial version of IBM Rational Developer for System z, software that can help you deliver core development capabilities; the power of Java Platform, Enterprise Edition (Java EE); and rapid application development support to diverse enterprise application development teams. With comprehensive development tools to help create, deploy and maintain traditional enterprise and composite applications, Rational Developer for System z enables developers with different technical backgrounds to easily participate in important technology projects. FREE! Go There Now!
|
|
|
|
Join us for this web seminar to learn how you can defend your web applications from attack. Learn about the 3 most common web application attacks, including how they occur and what can be done to prevent them. We’ll also discuss manual versus automated approaches for scanning and identifying web application vulnerabilities and how IBM Rational AppScan, an automated vulnerability scanner, can help you automate more of what you are doing manually today. FREE! Go There Now!
|
|
|
|
Learn from the best! Find out how developers use Rational ClearCase to be more flexible, innovative and deliver higher quality code in the Rational ClearCase Power Users eKit. This complimentary eKit provides a collection of materials, like articles, whitepapers, and demos that can help you become a power user of Rational ClearCase. FREE! Go There Now!
|
|
|
|
Get a free trial download of the latest version of IBM Rational Performance Tester V7.0.1, a load and performance testing solution for teams concerned about the scalability of their Web-based applications. Combining multiple ease-of-use features with granular detail, Rational Performance Tester simplifies the test-creation, load-generation and data-collection processes that help teams ensure the ability of their applications to accommodate required user loads. FREE! Go There Now!
|
|
|
|
Viper 2 brings a great value to developer communities including SQL, XML, PHP, Ruby, .NET and Java. You probably already know that DB2 Express-C is free for developers to develop, deploy and distribute. Viper 2 provides a variety of means that help move your application from the development stage to deployment more rapidly. This webcast shows how to best utilize the latest tools available for developing DB2 applications. FREE! Go There Now!
|
|
|
|
All FREE IBM® developerWorks Tools! |