Tutorial.Non-PHP integration

=Integrating phpBB3 With Non-PHP Websites= This article describes a technique for creating a more transparent end-user experience when a phpBB3 forum is desired for a site based on a technology other than PHP. The method presented here uses phpBB3 as the identity authority for the site since the goal which led to this technique was to minimize phpBB3 changes. However, some elements of this approach may also prove useful to anyone who wishes to go the other way, and make the main site drive phpBB3.

This example uses classic ASP as the non-PHP environment, although the technique should be applicable to just about any common website server-side pre-processing environment.

Theory of Operation
Since phpBB3 is being used as the identity authority, this means the main site performs logins by asking phpBB3 to test the user name and password against the phpBB3 user database. If the login request is successful, the PHP code will set a cookie which permits the non-PHP server-side code to verify that the user is authorized to view the content. A convenient side-effect is that the user will also be logged into phpBB for the duration of the PHP session timeout, so if the user navigates to the forum no extra, separate login is required. (Of course, if the user has chosen the "Log me in automatically" option, they won't be prompted anyway.)

Since PHP is a server-side pre-processor which concludes execution by sending a result back to the client, the method needs a way to securely communicate success or failure through the client. It is not enough to simply send a success or failure indicator back to the client: that leaves the decision about how to proceed up to the client, which is obviously not secure. The non-PHP environment must be able to perform some type of server-side verification of a successful login attempt.

The solution is to rely upon information that is available to both server-side environments, but is never available to the client system, and to pass a hashed version of that information through the client for verification. This is accomplished through web browser cookies. When a user logs in successfully, the PHP code generates a hashed value from the phpBB3 user database and stores that value in a cookie. The protected non-PHP page can then read that hashed value back from the cookie and compare it to the same phpBB3 user table with a simple database query.

Sequence of Operation
The order of events in this sequence is as follows:


 * 1) Client: The user browses to protected ASP content.
 * ASP: Server script does not find an authorization cookie, so it redirects the user to a login page.
 * 1) Client: The user provides his user name and password in a form which submits to a PHP file.
 * PHP: Call the phpBB3 login function, write the cookie if successful, and redirect back to the protected ASP content.
 * 1) Client: Redirect is processed and the browser again navigates to the protected ASP page.
 * ASP: The authorization cookie is found, the content is authenticated, the protected content is served.
 * 1) Client: User views protected content.

Note that once the authorization cookie is generated, the user will remain authorized to access the protected content until either the user record in the phpBB3 database changes, or until the cookies are expired in some other fashion. This is an implementation detail which is not covered in this article.

Since the non-PHP content is already accessing the phpBB3 user table, that part of the site can also leverage the other user information in that table -- the user name, e-mail address, and so on. If the site needs to store additional data, either the user table can be extended, or a separate table can be maintained. Those are also implementation details which are not detailed here.

Cookie Contents and Verification
Two values are stored into the cookie: the phpBB3 user ID number, and a hash value which is generated from the user's password hash and the user's e-mail hash. Those two hashes are stored in the phpBB3 user table and are never made available to the client, so they are a good choice as inputs to the authorization process. The PHP code combines those two hashes and generates a new MD5 hash, and that is stored into the cookie. That value is referred to as the authorization key.

When the ASP page needs to verify the authorization, it reads the user ID number and accesses the phpBB3 database to read the user's record from the user table. Assuming the user ID is valid, it retrieves the password hash and the e-mail hash, combines them, generates an MD5 hash, and compares it to the authorization key from the cookie. If they are identical, this confirms the login is valid.

=Sample Code= The example shown here must be modified before it will work with your website.

This is a very basic sample intended only to demonstrate how the technique works. To run the sample with your own site, you must:


 * alter the domain name to match your site name,
 * alter the path to your phpBB3 installation,
 * alter the database connection string to point to your phpBB3 database.

The domain name used in the sample code is localtest.com.

class_md5.asp
Since classic ASP does not have built-in support for MD5, a freeware class written by Chris Read called ASPMD5 is used for this example. For test purposes, put the class_md5.asp file into your website root directory.

protected.asp
This file represents the protected content the user wishes to view. The ASP code shown below would be included on any page you wished to protect, probably in a separate include file. Save it to your website's root folder for the purposes of this demonstration. Once the demonstration is set up, this is the page you will access with your browser. To illustrate that the page has accessed the correct user, the output is the text, "Authorization granted to username," upon successful login.

<%

'Access to this page is protected.

Dim sUID, sKey, sAuth, oMD5 Dim sConnect, oConnect, oRS Dim sUsername

'Look for a phpBB user ID and authentication key set by PHP. sUID = Request.Cookies("phpbb_authUID") sKey = Request.Cookies("phpbb_authkey")

'No authentication key, redirect to login page. If sUID = "" Or sKey = "" Then Response.Redirect("http://www.localtest.com/login.asp?target=http://www.localtest.com/protected.asp&msg=Provide your phpBB login:") Response.End

Else 'Test authentication key against database sConnect = "Driver={MySQL ODBC 5.1 Driver};Server=SERVERNAME;Database=PHPBBDATABASE;User=USERNAME; Password=PASSWORD;Option=3;Port=3306;" Set oConnect = Server.CreateObject("ADODB.Connection") oConnect.Open(sConnect) Set oRS = oConnect.Execute("SELECT * FROM phpbb.phpbb_users WHERE user_id = " + sUID) 'Invalid user ID, redirect to login page If oRS.EOF Then oRS.Close Set oRS = Nothing oConnect.Close Set oConnect = Nothing Response.Redirect("http://www.localtest.com/login.asp?target=http://www.localtest.com/protected.asp&msg=Invalid user ID, try again:") Response.End

Else 'Generate authentication key from database values sUsername = oRS.Fields("username") Set oCrypt = New MD5 oCrypt.Text = (oRS.Fields("user_email_hash") & oRS.Fields("user_password")) sAuth = oCrypt.HexMD5 Set oCrypt = Nothing oRS.Close Set oRS = Nothing oConnect.Close Set oConnect = Nothing 'Key mismatch, redirect to login page If sAuth <> sKey Then Response.Redirect("http://www.localtest.com/login.asp?target=http://www.localtest.com/protected.asp&msg=Authorization failed, try again:") Response.End End If	End If

'If we reach this point, the cookie authorization key matches the hash 'we generated directly from the database, so the user has logged in. 'Here we could test a timeout value, for example. The recordset and 'connection is closed but we could have grabbed other useful database 'columns from the phpbb_user table as well. End If %> Access granted to user <%=sUsername%>

login.asp
The login page is very simple. The key factor here is that the user's entries are passed to a PHP file in the phpBB3 forum directory structure. Store this file in your website root folder as well. For the simple needs of this demo, the page takes two query-string parameters

The first parameter is "target" which identifies the URL of the protected content. This is passed along to the PHP authentication page so that the PHP side knows where to send the user if the login succeeds. Depending on the needs for the site you are building, it is perfectly secure to pass this URL as a query-string parameter. After all, by definition, that page is protected -- and it is the page the user just tried to access.

The second parameter is "msg" which is a simple text message shown as a prompt for the user. Throughout this test you may see the following messages on the login page:


 * Provide your phpBB login
 * Invalid user ID, try again
 * Authorization failed, try again
 * Invalid login, try again

Each of these message are generated from one of the files in this example. You will probably want to create more robust interactions for a real implementation of this process. The first three are generated by protected.asp and the last is generated by userauth.php.

<%=Request.QueryString("msg")%>  Username  Password   Autologin  ">

userauth.php
This is the file which actually tests the supplied credentials. Put this into your top-level phpBB3 forum directory. It takes a "target" query-string parameter as well.

<?PHP

define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpEx = substr(strrchr(__FILE__, '.'), 1); require($phpbb_root_path . 'common.' . $phpEx);

$username = $_POST['username']; $password = $_POST['password']; $remember = $_POST['autologin']; $navtarget = $_POST['navtarget'];

$user->session_begin; $auth->acl($user->data); $user->setup;

$auth->login($username, $password, $remember, 1, 0);

// Respond to a login failure if ($user->data['user_id'] == ANONYMOUS) {	header('Location: http://www.localtest.com/login.asp?target=' . $navtarget . '&msg=Invalid%20login,%20try%20again:'); }

// Respond to a successful login else {	// This would send the user to the phpBB forum, already logged in. // redirect(append_sid("{$phpbb_root_path}index.$phpEx"));

// We timeout the authentication elsewhere so set the cookie // expiration to far in the future (10 years) $expire = time + 315360000;

// Put the user ID into an ASP-readable cookie and put a safely-hashed // key into another cookie that ASP can reproduce on the other side // for verification purposes. $key = md5($user->data['user_email_hash'] . $user->data['user_password']);

setcookie("phpbb_authUID", $user->data['user_id'], $expire, "/", "localtest.com"); setcookie("phpbb_authkey", $key, $expire, "/", "localtest.com");

header('Location: ' . $navtarget); }

?>

killcookies.asp
This file is not necessary for the technique but since the authorization cookie is permanent in this sample, this is useful for erasing the cookie so you can run through the process again.

<%	Response.Cookies("phpbb_authUID").Domain = "localtest.com" Response.Cookies("phpbb_authUID").Path = "/" Response.Cookies("phpbb_authUID").Expires = #May 1, 1999#

Response.Cookies("phpbb_authkey").Domain = "localtest.com" Response.Cookies("phpbb_authkey").Path = "/" Response.Cookies("phpbb_authkey").Expires = #May 1, 1999# %> Cookies deleted.

=Notes, Issues, Enhancements=

IIS Cookie/Redirect Bug
All Microsoft IIS releases prior to version 7 have a bug which causes cookies to be lost when a redirect header is emitted. This is documented in the Microsoft Knowledge base as article 176113. Fortunately the solution is simple: remove the redirect at the end of userauth.php and emit a single line of client-side script to navigate the user back to the protected content. The last few lines of userauth.php shown above would then look like this:

setcookie("phpbb_authUID", $user->data['user_id'], $expire, "/", "www.theaterops.com"); setcookie("phpbb_authkey", $key, $expire, "/", "www.theaterops.com");

// Removed for IIS bug; reinstate and remove client code after upgrade to IIS7+ // header('Location: ' . $navtarget); } ?>  window.location="<? echo $navtarget; ?>"

Maximum Login Attempts
This example does not take any special action when the user exceeds the maximum number of login attempts. The phpBB3 login routine will note this condition and block all future login attempts. Normally under phpBB3 the user would be sent to a special re-authentication page which requires login plus CAPTCHA verification. This condition should probably be trapped and addressed in a real-world implementation. In the example, once you exceed the maximum number of login attempts, even the correct credentials will be treated as a login failure. You will then have to log into phpBB3 itself and go through the re-authentication process.

Expiration of Authorization
This example sets a very arbitrary 10-year expiration on the authorization cookie. As noted in the article, the way the example is written, once the user is authorized, they are effectively authorized permanently as far as the non-PHP side is concerned. The only thing which would change that is if the e-mail address changed. That would change the e-mail hash code, and the authorization key would no longer match. Although you could simply put a shorter expiration on the cookie, it may be more useful to provide an alternative expiration method controlled by the user's database record. Since the non-PHP code reads the phpBB3 user record anyway, one convenient option would be to compare the current date against one of the last-activity timestamps stored in the user record.

Expiration of PHP Session
When the PHP code authenticates the user, a real phpBB3 login session is started. However, the point of this technique is to allow the user to engage in activities supported outside of the PHP environment. This means the PHP session is not "kept alive" by additional user activity, so the session will time out according to whatever limits are set for phpBB3 and/or PHP itself. This means there is a "risk" that the user will be logged in through the main site, but asked to login again if they visit the forum. (Obviously this will not happen if the user opted to log into the forum automatically.) It should be possible to build in a simple "keep-alive" mechanism whereby the ASP code periodically loads up a PHP file to refresh the session timeout countdown. This could even be done transparently with a simple call through XMLHTTPRequest.

Initial phpBB3 Account Creation
The process shown here also implies the user's account is initially created within phpBB3. You should take steps to alter the phpBB3 pages associated with that process so the user does not become confused about whether they are registering for a discussion forum only, or for the overall site as a whole. This should be the only place where phpBB3 modifications are somewhat important to implementing this method of integration.

Initial Main Site Account Creation
If the main site needs to maintain additional user information that is too extensive to reasonably add to the phpBB3 user table, all the main site needs to do is check for a matching user record in the main site's database when the phpBB3 user account is verified. If no record is found, simply create a new one. Remember to associate the main site's record with the phpBB3 user ID for later use.

More Extensive Integration
Several points come to mind where phpBB3 probably should be tweaked to create the most seamless user experience. Themes can be applied, of course, which will make it less obvious that the forum is "a separate place" -- moving things like the login and registration links out of the forum area and "up" into the main site. In some cases, phpBB3 will navigate the user to parts of the forum when a more integrated solution might send them somewhere else. Examples are where the user "lands" after initial registration, or the sequence of events when the user asks to have his password e-mailed to him. A more elaborate example would be auto-generating the authorization key when the user auto-logins (imagine, for example, that the user stored a bookmark to a forum topic).

=See Also=
 * External Login
 * Add custom page