Php WebSessions

Download the source archive WebSessions.zip. Install it as a Php Application With Existing Sources or From Remote Server. On MAC or Linux run this command in WebSessions to ensure web writability:
$ chmod 777 app/cache

Session Management

Session management is a mechanism for maintaining and sharing the state of variables (called session variables) for every participating program in a web project. These values of session variables are not lost when you go offsite and return. This type of web programming is called stateful in contrast to the default stateless programming based on input parameters only. Statefulness is one of the key ingredients of user-authenticated web access or web-based shopping carts used in e-commerce.

The server can maintain a relationship with a browser session by sending and receiving information packets called cookies. A cookie is simply a name/value pair of data which contains other attributes such as the target URL (server name and data source) and expiration timestamp.

In session management using cookies, when a browser first contacts a web server, the server responds by sending a cookie called the session id which is stored in the browser. The value of the session id is a fixed-length sequence of hexadecimal digits computed from the initial browser request. The session id serves to identify the browser connection, and the server associate persistent information maintained in a variety of possible storage mechanisms. When the browser makes a subsequent contact to the server in which the cookie's URL matches the server data source, the cookie is sent to the server and the session information is made available.

Php Session Management

Session handling in Php uses cookies with the name PHPSESSID. As mentioned above, the cookie is originally sent from the server to the browser and then subsequently from browser back to the server. The default lifetime for a PHPSESSID cookie is the browser session, i.e., as long as the browser remains open. A Php program initiates session management by calling the function:
session_start();
From then on, the $_SESSION array map is available to maintain any number variables defined through string keys. The session_start function must be called prior to the echoing of any HTML code so that the cookie can be placed in the response header. Consequently, any file which employs sessions must begin with <?php as the very first characters in the file, i.e., no other characters (even white space) prior to this. Once a session has been established and we set
$_SESSION['name'] = /* some serializable value */;
then $_SESSION['name'] is available to all participating scripts. The "statefulness" of the $_SESSION array is actually achieved by serializing it, i.e., converting it into a string. When a session-based response begins, the serialized session string is unserialized, i.e., converted back to an array map. Consequently only serializable data may be stored as values of session keys (i.e., no files, I/O streams, database entities, etc.).

Restrict applicability of a session variable

Imagine that you are using the valid session variable to protect access to a certain web page. After an authentication step, you set:
$_SESSION['valid'] = 1
Then access to the page is permitted only after successfully testing:
if ( isset($_SESSION['valid']) ) {
  // I can access some protected resource
}
The problem is that if multiple applications use this same strategy, then you are subject to a "bleed over" effect where authentication in one application permits access in another one. One way to deal with this to set the session cookie's applicability path using the function session_set_cookie_params prior to the session_start() call. A better approach to restricting the applicability of a session variable to a single web application is making $_SESSION be two-dimensional in the form:
$_SESSION[_KEY_]['valid'] = ...
where _KEY_ is a unique "secret" constant used only by scripts within the web application. In more sophisticated settings, the _KEY_ should be rotated, i.e., changed periodically to avoid someone gaining access to the session.

A Session class

Because of the syntactic complexity of using session variable of the form $_SESSION[_KEY_]['param'], it is easier and less error prone to wrap the session operations used as member functions in a dedicated class. In particular we will make extensive use of the included file:

include/session.php
<?php
session_start();
 
define( "_SECRET_", "-set-this-to-make-sessions-secure-" );
define( "_HASH_", md5(__DIR__ . _SECRET_) );
define( "_KEY_", "sess_" . _HASH_ );
 
class Session {
  public function __set($name,$value) {
    $_SESSION[_KEY_][$name] = $value;
  }
  public function & __get($name) {
    return $_SESSION[_KEY_][$name];
  }
  public function __toString() {
    return isset($_SESSION[_KEY_]) ? print_r($_SESSION[_KEY_],true) : "null";
  }
  public function __isset($name) {
    return isset($_SESSION[_KEY_][$name]);
  }
  public function __unset($name) {
    unset($_SESSION[_KEY_][$name]);
  }
  public function unsetAll() {
    unset($_SESSION[_KEY_]);
  }
}
 
$session = new Session();
The _KEY_ constant is created as a hash string using the md5 function. The input string combines the full path to session.php (the Php special constant __DIR__), plus an optional _SECRET_ string. The Php "magic methods" establish this relationship between usage and underlying behavior:
$session->var = 17;        // $session->__set("var",17)
                           //   => $_SESSION[_KEY_]['var'] = 17
 
$value = $session->var;    // $value = $session->__get('var')
                           //   => $value = $_SESSION[_KEY_]['var'];
 
if (isset($session->var))  // if ($session->__isset('var')) 
                           //   => if (isset($_SESSION[_KEY_]['var'])) 
 
unset($session->var)       // $session->__unset('var');
                           //   => unset($_SESSION[_KEY_]['var']);
 
echo "$session";           // echo $session->__toString();
The most unusual of these methods is __get which returns the variable by reference. Return by reference is necessary to make session array variables work, e.g.:
$session->some_var[] = "foo";
One strange side-effect of return-by-reference in __get is that attempting to use an undefined session key will make the key appear, associated with the null value. It is still undefined according to the isset test.

Session Access in Smarty

Session variables need to be made accessible to the Smarty template view script as well as the controllers. For example, we want certain features within views scripts to appear only for validated users. A $session variable must be accessible to the links.tpl template file which generates the menu items.

Although every controller could create and pass a $session variable to its template, a better approach is to load $session through the initialization mechanism. This is already achieved: Towards this end, we use the following Smarty plugin file:

include/smarty.php
<?php
...
 
require_once __DIR__ . "/session.php";
 
$smarty->assign('session', $session);
This is what makes $session available to smarty templates so long as the program includes this file. You can see two usages in the common template files:

templates/layout.tpl
...
    <div>
      <img class="img-responsive" src="img/header.png" />
      <span>{$session->valid->user|default}</span>
    </div>
...
and

templates/links.tpl
...
{if $session->valid}
  <li><a href="protected.php">Protected</a></li>
  <li><a href="logout.php">Logout</a></li>
{else}
  <li><a href="login.php">Login</a></li>
{/if}

Redirection and Reload

Redirection means telling the browser to go to another location instead of the one currently accessed. It is achieved in Php by the Php header function like this:
header("location: some_other_script.php");
A script which intends to use this should avoid generating any output prior to this call. The remainder of the program is executed, but effectively ignored because the browser is told to execute the script:
some_other_script.php
It is usually best practice to exit the script after a redirect in order to avoid any unintended side-effects of proceeding on in the script, i.e., header function like this:
header("location: some_other_script.php");
exit();
Redirects are especially important when sessions are used. In particular a script can "achieve its desired effect" simply by setting a session variable and then redirecting elsewhere. This is precisely what happens in the validate script discussed below.

The redirection can go back to the current script, in which case it is called a reload. Doing a reload must be written in a way to avoid an "infinite" sequence of reloads, something like this:

this_script.php
...
if (isset($params->myparam)) {
  $myparam = $params->myparam;
  // use $myparam in some way, like setting a session variable
  ...
  // reload, but with no parameters to avoid coming back here
  header("location: this_script.php");
  exit();
}
...
The reload is called without any parameters to avoid falling into the if block and calling reload again.

Session and Flash variables

The home page displays the two common usages of session variables: The index page reveals the two session variables $session->foo and $session->bar which are read and printed by this page. The latter one, $session->bar, is not used directly, but indirectly by the session_get_flash function. The "flash" aspect means it is only read once, and then disappears. All web frameworks support flash session memory, although, unlike ours, the values may "live" across only one redirect; in our case, the value is "alive" until it is read.

templates/index.tpl
{extends file="layout.tpl"}
 
{block name="content"}
 
<h2>Home</h2>
 
value of <tt>foo</tt>: <tt>{$session->foo}</tt>
<br />
<br />
value of <tt>bar</tt>: {session_get_flash var="bar"}
<br />
<br />
session:
<pre>{$session}</pre>
 
{/block}
The user-define smarty function session_get_flash does the work for us. It is defined as a by a Smarty plugin in the auto-loaded file:

include/myplugins/function.session_get_flash.php
<?php
 
/*
  retrieve and clear the value of a session key.
  the value must be a scalar
 */
 
function smarty_function_session_get_flash($params, $smarty) {
  if (empty($params['var'])) {
    trigger_error("assign: missing 'var' parameter");
    return;
  }
 
  $session = $smarty->getTemplateVars('session');
  $var = $params['var'];
  $value = $session->$var;
  unset($session->$var);
  return $value;
}

The Session Variable

The $session->foo variable is set by a value created in a textfield and sent by a reentrant form.

setfoo.php
<?php
require_once "include/smarty.php";
require_once "include/session.php";
 
// print_r($_POST);
 
$doit = filter_input(INPUT_POST, 'doit');
 
if (!is_null($doit)) {
  $session->foo = filter_input(INPUT_POST, 'foo');
}
 
$data = [
   'foo' => $session->foo,
];
$smarty->assign($data);
$smarty->display("setfoo.tpl");

templates/setfoo.tpl
{extends file="layout.tpl"}
 
{block name="content"}
<h2>SetFoo</h2>
  <form action="{$smarty.server.PHP_SELF}" method="post">
    foo: <input type="text" name="foo" value="{$foo|escape:'html'}" />
    <button type="submit" name="doit">Submit</button>
  </form>
{/block}

The Session Flash Variable

The $session->bar variable is set by a value created in a select list. In this case, once the value is set, the script redirects to the home page where it is read via the user-defined session_get_flash causing its value to be deleted (i.e. null) for subsequent reads.

setbar.php
<?php
require_once "include/smarty.php";
require_once "include/session.php";
 
$choices = array('aaa' => 'aaa',  'bbb' => 'bbb', 'ccc' => 'ccc');
 
$doit = filter_input(INPUT_POST, 'doit');
 
if (!is_null($doit)) {
  $session->bar = filter_input(INPUT_POST, 'bar');
  header("location: ."); 
  exit();
}
 
$data = [
    'choices' => $choices,
];
$smarty->assign($data);
$smarty->display("setbar.tpl");

templates/setbar.tpl
{extends file="layout.tpl"}
 
{block name="content"}
<h2>SetBar</h2>
  <form action="{$smarty.server.PHP_SELF}" method="post">
    <select name="bar">
      {html_options options=$choices}
    </select>
    <button type="submit" name="doit">Submit</button>
  </form>
{/block}

Simple Password Authentication

Authentication is controlled by a session variable:
$session->valid 
If this is set, the browser is permitted to access "protected" pages. A script (like protected.php) controls this behavior by a crude, but effective method:
<?php
require_once "include/session.php";
 
if (!isset($session->valid)) {
  header("location: login.php");
  exit();
}
The purpose of "logging in" is to set $session->valid (to something) by proving that we know some secret information, i.e., the password. To do so we access the login.php script from the menu:

login.php
<?php
require_once "include/smarty.php"; // creates $session
 
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
 
if (isset($session->valid)) {
  header("location: .");
  exit();
}
 
$data = [
];
$smarty->assign($data);
$smarty->display("login.tpl");

templates/login.tpl
{extends file="layout.tpl"}
 
{block name="localstyle"}
<style type='text/css'>
  #message { color: red; }
</style>
{/block}
 
{block name="content"}
  <h2>Login</h2>
  <form action="validate.php" method="post">
    <input type="password" name="password" autofocus />
    <button type="submit">Submit</button>
  </form>
  <h3 id="message">{session_get_flash var="message"}</h3>
{/block}
The message flash variable is used to display an authentication error. Upon submission, we activate validate.php with the password parameter defined:

validate.php
<?php
require_once "include/session.php";
 
define("PASSWORD", "foobar");
 
$password = filter_input(INPUT_POST, 'password');
 
if ($password === PASSWORD) {
  $session->valid = new stdClass();
  $session->valid->user = "admin";
  header("location: .");
  exit();
}
$session->message = "Invalid";
header("location: login.php");
If the password entered is incorrect, we go back to login.php, displaying the flash failure message set by $session->message. On the other hand, if the password we entered is correct then we "succeed" by setting $session->valid:
$session->valid = "valid";
In this case, we redirect back to the home page. Now you will see the menus change, allowing access to protected.php and presenting logout, which is no more than:

logout.php
<?php
require_once "include/session.php";
 
unset($session->valid);
header("location: .");

Controller-only Scripts

Note that validate.php and logout.php are Php web scripts with no associated view. In both cases, the last step is to redirect elsewhere using the header function, setting the location to the target.


© Robert M. Kline