p2p.wrox.com Forums

Need to download code?

View our list of code downloads.


  Return to Index  

beginning_php thread: Sessions problem


Message #1 by "Anton Vorster" <avorster@k...> on Mon, 23 Sep 2002 20:24:24
Okay, I'm more than a bit foggy on the whole issue of sessions, esp. with 
register_globals set to "off". I've tried to adapt the access_logger.php 
script from Chapter 13, but I can't seem to register (or start?) a 
session.  I include the script at the beginning of every page I want to 
protect, but I have to login again when I go from one page to another.

This is what the script looks like at the moment:

<?php
//access_logger.php
include "./common_db.inc";
$exclude_dirs = array('/', '/info', '/contact');
$exclude_files = array('index.html', 'info.html', 'index.php');
$user_tablename = 'user';
$access_log_tablename = 'access_log';

function login_form() {

html_header();

?>

<BODY>
<FORM METHOD="POST" ACTION="<? echo $_SERVER['PHP_SELF'] ?>">
   <DIV ALIGN="CENTER"><CENTER>
      <H3 class="heading">Please log in</H3>   <TABLE WIDTH="240" 
BORDER="0" CELLPADDING="2" bgcolor="#bbbbee">
        <TR>
          <TH class="default" WIDTH="18%" ALIGN="RIGHT" NOWRAP>ID:</TH>
         <TD WIDTH="82%" NOWRAP>
            <INPUT TYPE="TEXT" NAME="userid" SIZE="14">
         </TD>
      </TR>
      <TR>
          <TH class="default" WIDTH="18%" ALIGN="RIGHT" 
NOWRAP>Password:</TH>
         <TD WIDTH="82%" NOWRAP>
            <INPUT TYPE="PASSWORD" NAME="userpassword" SIZE="14">
         </TD>
      </TR>
      <TR>
         <TD WIDTH="100%" COLSPAN="2" ALIGN="CENTER" NOWRAP>
            <INPUT TYPE="SUBMIT" VALUE="LOGIN" NAME="Submit">
         </TD>
      </TR>
   </TABLE>
   </CENTER></DIV>
</FORM>

<div align="center" class="default">
Not yet registered? Register <a href="register.php">here</a></div>
</BODY>
</HTML>
<?
}

function do_authentication() {
   global $PHP_AUTH_USER, $PHP_AUTH_PW;
   global $default_dbname, $user_tablename, $access_log_tablename;
   global $MYSQL_ERROR, $MYSQL_ERRNO;
   
   if(!isset($_POST[userid])) {
      login_form();
      exit;
   }

 	else  $_SESSION["userid"];
          $_SESSION["userpassword"];
  
   $link_id = db_connect($default_dbname);
   $query = "SELECT userfirstname FROM $user_tablename 
             WHERE userid = '$_POST[userid]' 
             AND userpassword = password('$_POST[userpassword]')";
   $result = mysql_query($query);

   if(!mysql_num_rows($result)) {
      session_unregister("userid");
      session_unregister("userpassword");
      echo "Authorization failed. " .
         "You must enter a valid userid and password combo. " .
         "Click on the following link to try again.<BR>\n";
      echo "<A HREF=\"$_SERVER[PHP_SELF]\">Login</A><BR>";    
      echo "If you're not a member yet, click on the " .
           "following link to register.<BR>\n";
      echo "<A HREF=\"$register_script\">Membership</A>";    
      exit;
   }
   else {
      $query = "UPDATE $user_tablename SET lastaccesstime = NULL
                WHERE userid = '$_POST[userid]'";
      $result = mysql_query($query);

      $num_rows = mysql_affected_rows($link_id);
      if($num_rows != 1) die(sql_error());

      $query = "SELECT userid FROM $access_log_tablename
                            WHERE page = '$_SERVER[PHP_SELF]' AND userid 
= '$_POST[userid]'"; 
      $result = mysql_query($query);
    
      if(!mysql_num_rows($result)) 
         $query = "INSERT INTO $access_log_tablename 
                         VALUES ('$_SERVER[PHP_SELF]', '$_POST
[exercise]', '$_POST[userid]', 1, 0, NULL, NULL, NULL)";

      else $query = "UPDATE $access_log_tablename 
                     SET visitcount = visitcount + 1, accessdate = NULL 
                     WHERE page = '$_SERVER[PHP_SELF]' AND userid 
= '$_POST[userid]'";

      mysql_query($query);

      $num_rows = mysql_affected_rows($link_id);
      if($num_rows != 1) die(sql_error());
   }
}

$filepath = dirname($_SERVER['PHP_SELF']);
$filename = basename($_SERVER['PHP_SELF']);

if($filepath == '') $filepath = '/';

$auth_done = 0;

for($j=0; $j < count($exclude_dirs); $j++) {
   if($exclude_dirs[$j] == $filepath) break;
   else {
      for($i=0; $i< count($exclude_files); $i++) {
         if($exclude_files[$i] == $filename) break;
         else {
            session_start();
            $_SESSION["userid"];
            $_SESSION["userpassword"];
            do_authentication();
            $auth_done = 1;
            break;
         }
      }
   }
   if($auth_done) break;
}
?>

I'd really appreciate it if somebody could explain where I'm going wrong!

Many thanks,
Anton
Message #2 by "Nikolai Devereaux" <yomama@u...> on Mon, 23 Sep 2002 13:44:01 -0700
Okay, a couple questions:


What do lines 59-60 do?

59>  	else  $_SESSION["userid"];
60>           $_SESSION["userpassword"];

And again, on lines 124 & 125:

124>             $_SESSION["userid"];
125>             $_SESSION["userpassword"];


I suspect the code at the bottom is where your problems lie...

Here's your code:

<quote>

$filepath = dirname($_SERVER['PHP_SELF']);
$filename = basename($_SERVER['PHP_SELF']);

if($filepath == '') $filepath = '/';

$auth_done = 0;

for($j=0; $j < count($exclude_dirs); $j++) {
   if($exclude_dirs[$j] == $filepath) break;
   else {
      for($i=0; $i< count($exclude_files); $i++) {
         if($exclude_files[$i] == $filename) break;
         else {
            session_start();
            $_SESSION["userid"];
            $_SESSION["userpassword"];
            do_authentication();
            $auth_done = 1;
            break;
         }
      }
   }
   if($auth_done) break;
}

</quote>


What this kind of looks like is that you're trying to enforce authentication if
the user is requesting a page that's NOT some combination of exclude_path and
exclude_file.

The loop doesn't look like it'll do it correctly, to me.  The
do_authentication() function also doesn't appear to SET anything, so you really
don't know if the user's been authenticated or not -- the only clue that a
login was valid is if the do_authentication() function didn't exit().


Try getting a paper and pencil (or whiteboard, or whatever) and draw out the
loop for yourself.  You have the values for $exclude_dirs and $exclude_files
for yourself.  Make up a reasonable $filename and $filepath, and keep track of
all the loops and variable values by hand.  This is a slow excersize, but
should teach you more about the flow of control and logic in your code than any
debugger or print statements will.


Take care,

Nik

Message #3 by "Anton Vorster" <avorster@k...> on Tue, 24 Sep 2002 19:11:26
> 
>Okay, a couple questions:


>What do lines 59-60 do?

>59>  	else  $_SESSION["userid"];
>60>           $_SESSION["userpassword"];

>And again, on lines 124 & 125:

>124>             $_SESSION["userid"];
>125>             $_SESSION["userpassword"];


Okay, I was pretty confused here.  I was actually trying to register 
$userid and $userpassword as session variables, but that's obviously not 
the way to do it!!!

So how does one do it?  Is it still:

   session_register("userid", "userpassword");

when register_globals=off?


I've gotten rid of the loop at the end of the script, since I don't need 
it.  The script is simply included at the top of every page I want to 
protect.

This is the script minus the loop and with $PHP_SELF changed to 
$_SERVER['PHP_SELF'].  It works with register_globals=on, but obviously 
not with register_globals=off:

<?php
//access_logger.php
include "./common_db.inc";

function login_form() {

?>
<HTML>
<HEAD>
<TITLE>Login</TITLE>
</HEAD>
<BODY>
<FORM METHOD="POST" ACTION="<? echo $_SERVER[PHP_SELF] ?>">
   <DIV ALIGN="CENTER"><CENTER>
      <H3>Please log in to access the page you requested.</H3>
   <TABLE BORDER="1" WIDTH="200" CELLPADDING="2">
      <TR>
         <TH WIDTH="18%" ALIGN="RIGHT" NOWRAP>ID</TH>
         <TD WIDTH="82%" NOWRAP>
            <INPUT TYPE="TEXT" NAME="userid" SIZE="8">
         </TD>
      </TR>
      <TR>
         <TH WIDTH="18%" ALIGN="RIGHT" NOWRAP>Password</TH>
         <TD WIDTH="82%" NOWRAP>
            <INPUT TYPE="PASSWORD" NAME="userpassword" SIZE="8">
         </TD>
      </TR>
      <TR>
         <TD WIDTH="100%" COLSPAN="2" ALIGN="CENTER" NOWRAP>
            <INPUT TYPE="SUBMIT" VALUE="LOGIN" NAME="Submit">
         </TD>
      </TR>
   </TABLE>
   </CENTER></DIV>
</FORM>
</BODY>
</HTML>
<?
}

function do_authentication() {
   global $PHP_AUTH_USER, $PHP_AUTH_PW;
   global $userid, $userpassword, $register_script;
   global $default_dbname, $user_tablename, $access_log_tablename;
   global $MYSQL_ERROR, $MYSQL_ERRNO;
   
   if(!isset($userid)) {
      login_form();
      exit;
   }
   else session_register("userid", "userpassword");
  
   $link_id = db_connect($default_dbname);
   $query = "SELECT userfirstname FROM $user_tablename 
             WHERE userid = '$userid' 
             AND userpassword = password('$userpassword')";
   $result = mysql_query($query);

   if(!mysql_num_rows($result)) {
      session_unregister("userid");
      session_unregister("userpassword");
      echo "Authorization failed. " .
         "You must enter a valid userid and password combo. " .
         "Click on the following link to try again.<BR>\n";
      echo "<A HREF=\"$PHP_SELF\">Login</A><BR>";    
      echo "If you're not a member yet, click on the " .
           "following link to register.<BR>\n";
      echo "<A HREF=\"$register_script\">Membership</A>";    
      exit;
   }
   else {
      $query = "UPDATE $user_tablename SET lastaccesstime = NULL
                WHERE userid = '$userid'";
      $result = mysql_query($query);

      $num_rows = mysql_affected_rows($link_id);
      if($num_rows != 1) die(sql_error());

      $query = "SELECT userid FROM $access_log_tablename
                            WHERE page = '$_SERVER[PHP_SELF]'
                            AND userid = '$userid'";      
      $result = mysql_query($query);
    
      if(!mysql_num_rows($result)) 
         $query = "INSERT INTO $access_log_tablename 
                         VALUES ('$_SERVER[PHP_SELF]', '', '$userid', 1, 
0, NULL, NULL, NULL)";
      else $query = "UPDATE $access_log_tablename 
                     SET visitcount = visitcount + 1, start_time = NULL 
                     WHERE page = '$_SERVER[PHP_SELF]' AND userid 
= '$userid'";

      mysql_query($query);

      $num_rows = mysql_affected_rows($link_id);
      if($num_rows != 1) die(sql_error());
   }
}

session_start();
do_authentication();

?>

What I'm really confused about now, is when to use $_SESSION[variable] 
and when to use $_POST[variable].

And what do I do about all the global variables under do_authentication?

I really appreciate your help, Nik.

Regards,
Anton
Message #4 by "Nikolai Devereaux" <yomama@u...> on Tue, 24 Sep 2002 11:31:15 -0700
> Okay, I was pretty confused here.  I was actually trying to register
> $userid and $userpassword as session variables, but that's obviously not
> the way to do it!!!

You don't have to register any variables with the session -- you're explicitly
saying that certain key/value pairs are session variables when you write them
to $_SESSION.

To illustrate the point, here's a php version of session_register:

function session_register($varname)
{
    $GLOBALS[$varname] = & $_SESSION[$varname];
}

See?  It creates a global variable named $varname which is just a reference
into the session array.


> So how does one do it?  Is it still:
>
>    session_register("userid", "userpassword");
>
> when register_globals=off?

So the simple answer is "one doesn't."

> This is the script minus the loop and with $PHP_SELF changed to
> $_SERVER['PHP_SELF'].  It works with register_globals=on, but obviously
> not with register_globals=off:

   <snippy>

> function do_authentication() {
>    global $PHP_AUTH_USER, $PHP_AUTH_PW;
>    global $userid, $userpassword, $register_script;
>    global $default_dbname, $user_tablename, $access_log_tablename;
>    global $MYSQL_ERROR, $MYSQL_ERRNO;

Two things -- I thought we got rid of the $MYSQL_ERROR and $MYSQL_ERRNO
variables.  The other is that your userid, userpassword, etc... variables are
still being imported as globals.  These are form input variables, and with
register_globals = off, they're only to be found in $_GET or $_POST, based on
the form method.

You can also use $_REQUEST, which has been the subject of some discussion
recently, but I advise against it.


Take care,

Nik

Message #5 by "Anton Vorster" <avorster@k...> on Wed, 25 Sep 2002 02:28:16 +0200
Thanks again, Nik.

This is what I now have (and it's still not working!):

<?php
//access_logger.php
include "./common_db.inc";

function login_form() {
?>
<HTML>
<HEAD>
<TITLE>Login</TITLE>
</HEAD>
<BODY>
<FORM METHOD="POST" ACTION="<? echo $_SERVER[PHP_SELF] ?>">
   <DIV ALIGN="CENTER"><CENTER>
      <H3>Please log in to access the page you requested.</H3>
   <TABLE BORDER="1" WIDTH="200" CELLPADDING="2">
      <TR>
         <TH WIDTH="18%" ALIGN="RIGHT" NOWRAP>ID</TH>
         <TD WIDTH="82%" NOWRAP>
            <INPUT TYPE="TEXT" NAME="userid" SIZE="8">
         </TD>
      </TR>
      <TR>
         <TH WIDTH="18%" ALIGN="RIGHT" NOWRAP>Password</TH>
         <TD WIDTH="82%" NOWRAP>
            <INPUT TYPE="PASSWORD" NAME="userpassword" SIZE="8">
         </TD>
      </TR>
      <TR>
         <TD WIDTH="100%" COLSPAN="2" ALIGN="CENTER" NOWRAP>
            <INPUT TYPE="SUBMIT" VALUE="LOGIN" NAME="Submit">
         </TD>
      </TR>
   </TABLE>
   </CENTER></DIV>
</FORM>
</BODY>
</HTML>
<?
}

function do_authentication() {
   global $PHP_AUTH_USER, $PHP_AUTH_PW;
   global $register_script;
   global $default_dbname, $user_tablename, $access_log_tablename;

   if(!isset($_SESSION[userid])) {
      login_form();
      exit;
   }

   else {
     $_SESSION['userid'] = $_POST['userid'];
     $_SESSION['userpassword'] = $_POST['userpassword'];
  }
   $link_id = db_connect($default_dbname);
   $query = "SELECT userfirstname FROM $user_tablename
             WHERE userid = '$_POST[userid]'
             AND userpassword = password('$_POST[userpassword]')";
   $result = mysql_query($query);

   if(!mysql_num_rows($result)) {
      session_unregister("userid");
      session_unregister("userpassword");
      echo "Authorization failed. " .
         "You must enter a valid userid and password combo. " .
         "Click on the following link to try again.<BR>\n";
      echo "<A HREF=\"$PHP_SELF\">Login</A><BR>";
      echo "If you're not a member yet, click on the " .
           "following link to register.<BR>\n";
      echo "<A HREF=\"$register_script\">Membership</A>";
      exit;
   }
   else {
      $query = "UPDATE $user_tablename SET lastaccesstime = NULL
                WHERE userid = '$_POST[userid]'";
      $result = mysql_query($query);

      $num_rows = mysql_affected_rows($link_id);
      if($num_rows != 1) die(sql_error());

      $query = "SELECT userid FROM $access_log_tablename
                            WHERE page = '$_SERVER[PHP_SELF]'
                            AND userid = '$_POST[userid]'";
      $result = mysql_query($query);

      if(!mysql_num_rows($result))
         $query = "INSERT INTO $access_log_tablename
                         VALUES ('$_SERVER[PHP_SELF]', '$_POST[userid]', 1,
NULL)";
      else $query = "UPDATE $access_log_tablename
                     SET visitcount = visitcount + 1, accessdate = NULL
                     WHERE page = '$_SERVER[PHP_SELF]' AND userid 
'$_POST[userid]'";

      mysql_query($query);

      $num_rows = mysql_affected_rows($link_id);
      if($num_rows != 1) die(sql_error());
   }
}

session_start();
do_authentication();
?>

As you can probably tell, my understanding of what's going on here is pretty
vague, so please dumb down any explanations as much as possible!

Much obliged,
Anton

Message #6 by "Nikolai Devereaux" <yomama@u...> on Tue, 24 Sep 2002 20:00:21 -0700
There's an easy explanation for why your script is still not working...


> function do_authentication() {
>    global .....;
>
>    if(!isset($_SESSION[userid])) {
>       login_form();
>       exit;
>    }
>
>    else {
>      $_SESSION['userid'] = $_POST['userid'];
>      $_SESSION['userpassword'] = $_POST['userpassword'];
>   }


Look at that very carefully -- the first thing you do is check to see whether
$_SESSION['userid'] is set.  If it isn't, you echo the login form and exit the
script.

If it _IS_ set, then you... well, set it.

See, there's no where that $_SESSION['userid'] is set before you check to see
if it has been.  You most likely meant to check if $_POST['userid'] was set.

Also, don't forget your quotes around string indexes!!


Take care,

Nik


p.s.  further advice -- make use of debugging statements!  For example:

echo "query \"$query\" returned \"$result\"";

if you have a failing query, you'll see something like:

query "SELECT * FROM WHERE x = 'y'" returned ""

That can be a big help in figuring out why things are failing.  In my fake
example above, you notice that there's no table name set -- that usually is a
sign of a misspelled variable name.

Message #7 by "Nikolai Devereaux" <yomama@u...> on Wed, 25 Sep 2002 09:37:58 -0700
Hi Anton,

Okay, I see the problem -- sorry I misled you.

Now that we're checking to see if $_POST[user_id] is set, we're getting a
little farther in the script and it actually process the rest of
do_authentication.

However, once we're authenticated, do_authentication() will always return the
login form since there's no reason to keep posting the user_id!

This is just another example of the book using a lot of program logic that,
IMHO, just does NOT make sense.

What we really need to check is whether the user is logged in or not.  If they
are NOT logged in, then display the login form.

See, do_authentication basically wastes a lot of time -- every time the script
executes, it's trying to pull the userid and password from the database to
verify them, and log the access.

Since register_globals copies BOTH the post and session variables to global
scope, the old line

   if(!isset($userid]))

is really checking

   if(!isset($_POST['userid']) && !isset($_SESSION['userid']))


Transated to english, what you're really saying is

"If the user isn't attempting to log in now with a userid, AND hasn't logged in
with a userid, then display the login form and exit."

This is logically equivalent to the following statement, which reads a bit
better in English:

   if(! (isset($_POST['userid']) || isset($_SESSION['userid'])) )

"If the user is neither attempting to log in nor already has, display the login
form and exit."


The problem I have is that do_authentication is given too much responsibility.
It's an all-in-one solution which prevents it's logical components from being
reused.

While on that subject, the login form shouldn't include the </BODY></HTML>
pages, because it doesn't fit with what the function name implies it does --
which is echo (and/or return) the HTML text of the login form ONLY.

That login_form() includes the closing BODY and HTML tags basically forces you
to exit the script immediately after -- what other processing should be done
after you've outputted the end of the page, after all?  That means that
login_form() requires the user to exit() the script, since additional HTML
output would make no sense... but what if you wanted to insert a tagline or
copyright tag at the bottom of the page?  Can't do it anymore, not without
modfying login_form().  Now it should be more clear that login_form is taking
too much responsibility -- it's technically
"login_form_and_copyright_page_footer()" or something like that.


For now, to get your script to work, make the following changes to
do_authentication:


   if(!isset($_POST['userid'])) {
      login_form();
      exit;
   }

   $_SESSION['userid'] = $_POST['userid'];
   $_SESSION['userpassword'] = $_POST['userpassword'];


becomes:

   if(isset($_POST['userid']))
   {
      $_SESSION['userid'] = $_POST['userid'];
   }

   if(!isset($_SESSION['userid']))
   {
      login_form();
      exit();
   }



We don't need to check both POST and SESSION in the if(!isset..) because if
post was set, we copied it's value to session anyway.


I hope this clears things up a bit.  If I get time today, I'll write up and
send the "nik version" of the script.  Hopefully it will make more sense once
it's been reorganized a bit.



Take care,

Nik


  Return to Index