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()
         ?>
         

Usually, you write code like this into the top of the page you want to protect:

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).

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

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.

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:

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.

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).

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:

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:

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:

and check for these two variables in loginform.ihtml at the appropriate places.

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.

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.