The complete guide to authentication and user variables
Usually, you write code like this into the top of the page you want to protect:
<?php
page_open(array(
"sess" => "My_Session",
"auth" => "My_Auth",
"perm" => "My_Perm")); // see Perm docs for specifics
$perm->check("admin"); // see Perm docs
?>
<?php
page_close()
?>
How is the Auth class used usually?
Usually, you write code like this into the top of the page you want to protect:
<?php
page_open(array(
"sess" => "My_Session",
"auth" => "My_Auth",
"perm" => "My_Perm")); // see Perm docs for specifics
$perm->check("admin"); // see Perm docs
?>
<?php
page_close()
?>
How does $auth work internally?
When you access this page, the call to page_open() is being made as the first thing on that page. page_open() creates an instance of My_Auth named $auth and starts it. $auth then detects that you are not authenticated (how it does, I will explain below) and displays loginform.ihtml. $auth then exits the interpreter, so that is never being executed or displayed.
The user now sits in front of a loginform.ihtml screen, which is shown under the URL of the page the user originally tried to access. The loginform has an action URL, which just points back to itself.
When the user filled out the loginform and submits it, the very same URL is requested and the above page_open() is reexecuted, but this time a username and a password are submitted. When the $auth object is created and started, it detects these parameters and validates them, resulting in either a NULL value or a valid user id. If the validation failed, creating an empty user id, the loginform is displayed again and the interpreter exits. Again is not executed.
If a UID is returned, that UID and a timestamp are being made persistent in that session and $auth returns control to page_open(). When page_open() finishes, which it may or may not do, depending on the presence and result of an optional $perm check, is being executed or shown.
Later calls to other pages or the same page check for the presence of the UID and the timestamp in the sessions data. If the UID is present and the timestamp is still valid, the UID is retained and the timestamp is refreshed. On page_close() both are written back to the user database (Note: Authenticated pages REQUIRE that you page_close() them, even when you access them read-only or the timestamp will not be refreshed).
If the UID is not present ($auth->logout() or $auth->unauth() have been called, for example) or the timestamp has expired, $auth will again intercept page display and draw the loginform.
The only way to get into a page with an $auth object on it is to have a UID and a valid timestamp in your session data (Note: This is true even for default authentication. These create a dummy UID and timestamp in your session data).
How do $sess and $auth interact?
Your browser has a session cookie, named after your session class. This is the only thing that is ever shipped between your browser and PHPLIB, as far as core functionality is concerned. The session cookie value is used as a reference into active_sessions, to retrieve PHPLIB generated PHP code, which is then eval()ed and recreates your session variables within page_open().
Part of the $auth object now makes itself persistent and is retrieved when the $sess part of page_open() is being executed. This is just before the $auth part of page_open() gets its turn, so that $auth can rely on its persistent data being present when it is being called.
From the PHPLIB source you all know that $auth has only one persistent slot, called $auth->auth[], of type hash. This hash contains the slots uid, exp and uname. $auth->auth["uid"] is the currently authenticated user id, $auth->auth["exp"] is the currently active expiration timestamp (Unix time_t format) for that uid. $auth->auth["uname"] is completely irrelevant as far as the regular PHPLIB Auth class is concerned. It is relevant in the context of the supplied default Auth subclass Example_Auth, though.
So a session is authenticated, if it contains $auth->auth["uid"] != false and time() < $auth->auth["exp"]
The original Auth class as included in PHPLIB makes no assumptions at all on how a loginform looks or how and where uids come from. There is no code at all in Auth that ever checks anything but the above two conditions. It is your responsibility to modifiy a subclass of Auth in a way that these conditions can ever be met.
Auth helps you in doing this by calling its own function $auth->auth_loginform() when it wants to draw a loginform. Unfortunately this function is empty in Auth itself, so you have to provide an implementation for that. The suggested standard implementation in local.incs Auth subclass Example_Auth is
function auth_loginform() {
include("loginform.ihtml");
}
and you put your code into that file. We also provide sample code for that file, but you are not limited to that code and may write a loginform.ihtml as it meets your needs.
When the loginform has been filled in and submitted back by the user, Auth calls $auth->auth_validatelogin(). Again, this function is empty in Auth itself and so Auth by itself will never function correctly. You have to subclass Auth and provide your own implementation of $auth->auth_validatelogin() in local.inc to make it work.
What you actually do in that function is completely irrelevant to Auth itself. It only exspects that you either return false, if the user-supplied authentication data was invalid, or a user id, if the user could be validated. Auth then takes care to create the appropriate entries ($auth->auth["uid"] and $auth->auth["exp"]) in the session record.
I still do not understand! What am I supposed to code?
You write your code into local.inc, after you have removed the classes Example_Auth, Example_Default_Auth and Example_Challenge_Auth from that file (keep a copy around, just for reference).
You code a class called My_Auth and you use that name later in your calls to page_open as an argument to the auth feature, as show at the start of this message. Follow the standard rules for deriving persistent classes in PHPLIB when you create your code, that is, do it like this:
class My_Auth extends Auth {
var $classname = "My_Auth";
// we inherit $persistent_slots and do not need to modify it.
// later code is inserted here
}
Now configure the lifetime of the authentication, that is, how many minutes in the future shall the current value of $auth->auth["exp"] be? Also, name a database connector class and name the table that you will be using to check usernames and passwords.
// insert this code as indicated above.
var $lifetime = 15;
var $database_class = "DB_Example";
var $database_table = "my_special_user_table";
// later code is inserted here
Okay, now we have a basic implementation of My_Auth that is only lacking the required functions auth_loginform() and auth_validatelogin(). Our implementation of auth_loginform() will have access to all $sess variables by globaling $sess into our context (because these can come in handy) and to all $auth variables (via $this).
function auth_loginform() {
global $sess;
include("loginform.ihtml");
}
The loginform is free to do whatever it damn well pleases to
create a form for the user to supply the needed values for
authentication. It has access to anything $sess and
anything $this related.
The loginform will display some input fields for the user, for
example a given name, a surname and a password. When the form is
submitted back, auth_validatelogin() is being called. The
form values are global variables (or $HTTP_x_VARS[]) and
must be imported into $auth->auth_validatelogin(). Then,
$auth->auth_validatelogin() is free to do whatever it must
do to produce a unique identifier for that user (or return
false).
Suppose you created input fields named given_name, surname and
password. So go ahead, global $given_name, $surname
and $password and set $uid to false. Then create the
SQL needed to access you user table and retrieve the user record
from your database as indicated by $given_name and
$surname and $password.
The query may succeed, if a record with matching
$given_name, $surname and $password is present.
In that case return the uid, which uniquely identifies exactly
that (given_name, surname) pair. Else return false.
In code:
function auth_validatelogin() {
// import authentication data
global $given_name, $surname, $password;
$uid = false;
$query = sprintf("select uid
from %s
where given_name = '%s'
and surname = '%s'
and password = '%s'",
$this->database_table,
addslashes($given_name), addslashes($surname),
addslashes($password));
// $auth->db is our DB_Example database connection
$this->db->query($query);
// now check for any results
while($this->db->next_record()) {
$uid = $this->db->f("uid");
}
// either $uid is false now (no results)
// or set to the last retrieved value from the uid
// column.
// Anyway we are set now and can return control
return $uid;
}
Okay, that's all and useable now. There is room for some
improvements, though: First we did not retrieve permission data,
so this will not work, if we want to use the perm feature as
well.
This is easily changed: Modify the query to select uid,
perms instead of select uid alone. Of course, you may call
your perm column whatever you like, just adapt the SQL
accordingly. Also, add a line after the
$uid assignment so that the code looks like this:
$uid = $this->db->f("uid");
$this->auth["perm"] = $this->db->f("perms");
This will store the retrived perms value under the key
perm within the $auth->auth[] array. It will be kept
around in that place in case $perm is called and starts
looking for the current permissions of that user.
Another possible improvement becomes apparent when you try to
login and fail to do so correctly: auth_validatelogin()
returns false and you hit the loginform again. Empty loginform
that is, because we did not remember what you typed into the
given_name and surname fields before. If we remembered
what you typed, we could easily supply these values back to you
so that you can correct them. We would also be able to detect if
this is a second, third, ... attempt to login and display an
appropriate error message somewhere in that loginform to inform
the user of his or her typo. A convenient place to store these
values is the $auth->auth array, which is persistent
anyway.
Standard Example_Auth uses the field $auth->auth["uname"]
to store that value, but you may use any field and as many
fields as you like as long as you make sure not to clash with
any of the three officially used fields, uid, exp, and
perm.
Do not try to turn the global variables $given_name and
$surname into persistent variables by calling
$sess->register("given_name") and
$sess->register("surname")! Remember: These are form
variables! Never ever make form variables persistent and never
ever trust unvalidated user supplied from the Internet!
So add the folling code just below the "global" line:
$this->auth["gname"] = $given_name;
$this->auth["sname"] = $surname;
and check for these two variables in loginform.ihtml at the
appropriate places.
Ok, I did that and it works. I even understood it. Now, what exactly is that uid used for?
It is simply a token to indicate that the user is authenticated.
We use a different token for each user, so that we can decide
which user we are currently dealing with. You can think of the
uid as a primary key for your auth_user table (or whatever
it is being called in your current application). The (
given_name, surname ) tuple would also be a possible primary
key, albeit a compound one. It is the external, human-readable
(and probably sometimes very long) representation of the
internal uid. The password field is functionally dependent on
either of both key candidates.
The internal user id should never be presented to the user; the (
given_name, surname ) pair is much more natural to handle for the user
and easier to remember (A user who does not remember his or her name
would probably not be in a state of mind to operate the rest of the
application anyway :-).
The internal user id should always be used to identify a user
internally within an application, though. That is, because the
uid is of a fixed length and has a known form and structure, so
you can make assumptions. A given_name or surname may be of any
length and may contain about any character, so you probably do
not want to use this as a user-reference internally.
But is the uid used internally by PHPLIB?
Yes, if you make use of the user feature of
page_open(), that is, if you create user variables.
The User class is actually a subclass of Session. That
is, user variables are just like session variables. They are
even stored in active_sessions. The only difference is that
the session has a different name (it is called Example_User
instead of Example_Session, if you use the classes and names
supplied in local.inc).
And in Example_User, the user id of the authenticated user
becomes the session id in the active_sessions table. That
is the reason why we recommend md5(uniqid("abracadabra"))
style uids.