Logo  

CS479/579 - Web Programming II

Lesson 9

Creating a unique session ID:

As a prelude to creating our own authentication system, we need to create a session ID that a cookie will be set to. The purpose of the session ID is to uniquely identify you from all the other visitors to a site. Once the browser can identify you, state can be stored in a database or files. There are several ways we can employ to make a session ID:

The easy method:

exec("uuidgen", $sid);

This will set the string $sid with the output of the program uuidgen which generates a Universally Unique IDentifier, which is supposedly guaranteed to be unique in all of time and space across the entire universe. It might be more than we require for our purposes however. It also requires that there be a uuidgen program installed. UUIDs are about 128 bits of randomness, but the bits are encoded as a hex string with dashes, and thus could be smaller.

To generate a session ID ourselves we can try:

The harder method:

<?php
function createSID()
{
  // 6 bits worth of character data:
  $cookie_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-@";

  $len = strlen($cookie_chars)-1;
  $sid = "";
  // Loop to generate 126 bits of randomness as a string:
  for ($i = 0; $i < 21; $i++)
    $sid .= $cookie_chars[rand(0, $len)];

  return $sid;
}
?>

Keeping track of sessions

To keep track of login sessions, we need to store the session ID, perhaps along with a login name or other identifying information. What follows is a method I created to do ISU AD and local CS authentication.

First we create a session table. We assume that username is the identifier we are most interested in with respect to maintaining state between the server and the browser. Thus it should be possible for there to be multiple sessions for the same username (the same login being accessed from different computers.)

DROP TABLE IF EXISTS `session`;
CREATE TABLE `session` (
  `sid`         varchar(128)       NOT NULL DEFAULT '',
  `webkey`      varchar(64)        NOT NULL DEFAULT '',
  `username`    varchar(64)        NOT NULL DEFAULT '',
  `domain`      ENUM('ISU','CS')   NOT NULL DEFAULT 'ISU',
  `usrinfo`     varchar(1024)      NOT NULL DEFAULT '',
  `ipaddr`      char(16)           NOT NULL DEFAULT '0.0.0.0',
  `created`     timestamp          DEFAULT CURRENT_TIMESTAMP(),
  `expire`      bigint(24)         NOT NULL DEFAULT 0,
  `atime`       timestamp          NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY ( `sid` )
);


The purpose of the columns:

Column Purpose
sid The (unique) session ID
webkey Specific key associated with this session (allows multiple sites on same host)
username The username could be replaced with a user ID if we are using a local user table
domain Domain for the login (ISU = ISU AD, CS = local CS account)
usrinfo Additional user information (such as real name)
ipaddr IP address associated with this session
created Date when this session was created
expire When the session (the cookie) should expired
atime Last time the account was accessed

An example user table

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `uid`         int(10)        NOT NULL AUTO_INCREMENT,
  `username`    varchar(64)    NOT NULL DEFAULT '',
  `password`    varchar(256)   NOT NULL DEFAULT 'x',
  `usrinfo`     text           NOT NULL DEFAULT '',
  `created`     timestamp      DEFAULT CURRENT_TIMESTAMP(),
  `atime`       timestamp      NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY ( `uid` ),
  INDEX `username` ( `username` )
) AUTO_INCREMENT=1000;


A uid is often a good way to keep track of a user entry, as it is usually faster to lookup a particular user numerically rather than doing string comparisons against a username. Note how the auto increment is set to start at 1000, leaving room for low UIDs that could be used to indicate administrator/ privileged accounts.

The password field contains a hashed password, it should never contain a plain-text password. History is replete with a multitude of user databases that have been made public, always assume that yours will be next.

The usrinfo field is a JSON encoded field containing any additional user information that we might want to contain. Only base information or things that would be changed often (such as atime) really ought to be in the table itself, as tables are troublesome to add or subtract from, but a JSON object is a bit more flexible. Information that could be in usrinfo could include their real name, email address, access levels, etc.

The created and atime fields are the same as for a session, but with respect to the user account itself.

Password Hashing:

Passwords should be cryptographically hashed and salted when stored to prevent an attacker from gaining immediate access to someones password which may allow them access to other sites.

A salt is a random string that is "added" to a password to make it more difficult to discover the real password. Each password should have its own randomly generated salt.

A hash might be further perturbed by adding a cost function, i.e. the hash is fed into itself X number of times, often called "rounds".) Making the hashing much slower and computationally expensive.

Currently good algorithms might be blowfish (bcrypt) or sha256 or above is recommended. DES, MD5 and SHA1 should be considered unsafe and deprecated.

A hashed password might appear as:

$algorithm #$rounds=5000$somesalt$hashed password

Real hash:

$6$rounds=5000$somesalt$YID3kajQz9xL19czLNmg1Lt2JwM7Yt9PTP.tUPymFJckv/f9.jgKfR3YB1.nl//PtMT/EKyHH5iSLHx9V03W5.

generated with:

php -r 'echo crypt("gigo","\$6\$rounds=5000\$somesalt") ."\n" ; '

  • Outputs a SHA512 (algorithm #6) hash using "somesalt" for the salt with 5000 rounds.

  • Note that the $'s need to be escaped or single quoted to prevent PHP from trying to interpret them as variables for expansion.

In php we can use the crypt() or password_hash() (PHP >=5.5.0) functions:

crypt(string $password [, string $salt]);

password_hash(string $password, PASSWORD_DEFAULT);