Forum Login using cURL

on 4th April
  • forum login
  • curl login
  • phpbb2 forum login
  • remote login
  • login script
  • php header
  • curl cookies
In this article, we're going to walk you through how you might login to a forum such as phpbb2 programatically. First off, why would you care to do such a thing? Well, out of the box forum scripts such as SMF and phpbb2 won't make much impact on their own for simply that reason, that they are out of the box and a few clicks in cPanel or Plesk can get you up and running in a couple of minutes. Ordinarily, to pack any punch (and of course to be useful and provide value to the end user), the forum script of choice will need to be installed and integrated as part of a larger container site.

Now that that scenario has been painted, continue down this imaginary path ever so briefly. Say you have an existing members area with which you wish to integrate your new forum, it would be generally unacceptable practice to ask your users to first login to your existing members area and then once inside, ask them to login again to the forum.

We won't go over in detail the whole integration process here, just the actual login process which is definitely the most complicated aspect of integration. For the sake of completeness, the other aspects of integration include creating the record in the new forum when a user signs up to your existing 'members-only' area, deleting the record if their account becomes deactivated and ensuring database integrity on account management functions, i.e. the user changing their password, their username, language preferences &c.

At a high level of abstraction, this is what we are going to do; we are going to validate the supplied username and password using our existing login system. Then we are going to establish any session variables and cookies that we use in our existing system, i.e. you might set a cookie with the md5'd value of the users password along with their id and validate this each time the user requests a 'members-only' page. After that, we will construct a cURL request to our forum login page passing along the supplied username and password and a bit of other information which we'll get to shortly. We process the response including cookies and the session id and then we redirect over to our 'members-only' index page. Done and dusted!

Let's get down to it. We'll encapsulate this logic in a function called checkLogin which will take 3 parameters (the supplied username, the supplied password and a flag indicating whether the user would like us to remember him/her on subsequent visits). The function which logs a user into a phpbb2 forum might look like this:


/*
$pu = passed username
$pp = passed password
$pr = passed remember flag
*/

function checkLogin($pu, $pp, $pr)
{

// Validate the username password combo
// using the current system. This assumes
// that you are working off a table called
// members and that all user passwords are
// stored as md5'd strings.

$sql = "SELECT * FROM members WHERE username = '$pu' AND password = '".md5($pp)."' LIMIT 1;";
$result = mysql_query($sql, $GLOBALS['db']);
if (mysql_num_rows($result)>0)
{

// At this point, we have validated the
// user credentials, we should now
// perform any perfunctory login actions,
// such as setting cookies for remembering
// the user, establishing the session
// variables. Details on this are beyond
// the scope of this article.

setSession(mysql_fetch_array($result), $pr);

// This is where it gets interesting,
// construct an array of POST variables
// that will be passed to the login script
// of the phpbb2 forum. To modify this
// function for a different forum,
// download a HTTP packet sniifer (such
// as HTTPLook), login to your forum in
// the normal manner and see what POST
// variables are passed to the login
// script in the HTTP request. Then simply
// replicate those variables here.

$post_data = array();
$post_data['username'] = $pu;
$post_data['password'] = $pp;
$post_data['autologin'] = ($pr) ? 'on' : '';
$post_data['login'] = 'Log in';

// The forum login script may attempt to
// set cookies depending on what the login
// mode is (i.e. whether the user should be
// remembered or not). To facilitate that
// functionality, we need to define a
// temporary file to hold the contents of
// the cookie.

$cookie_new = tempnam("/tmp","MKUP");

// Construct the cURL request, the
// options to note here are:
// 1) The URL of the forum login script
// using CURLOPT_URL
// 2) The incorporation of post data using
// CURLOPT_POST/CURLOPT_POSTFIELDS
// 3) The cookie handlers, CURLOPT_COOKIEFILE
// and CURLOPT_COOKIEJAR
// 4) The inclusion of the response header
// in the result using CURLOPT_HEADER

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://mysite.com/forum/login.php");
curl_setopt($ch, CURLOPT_POST, 1 );
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_new);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_new);
curl_setopt($ch, CURLOPT_HEADER,1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// Storing the result of the request
// including the HTTP response header is
// highly recommended as it can be used most
// effectively to decipher what, if anything,
// has gone wrong.

$postResult = curl_exec($ch);

// Trigger any cURL related errors at this point

if (curl_errno($ch))
{
// Trigger error
}

// Close the cURL session

curl_close($ch);

// If everything went according to plan,
// the contents of any cookies the login
// script wanted to set will be stored in
// $cookie_new. At this point then we must
// actually set the cookies as simply requesting
// them via cURL will not set them on the client
// browser. Read the contents into a variable
// using the file_get_contents function.

$cookie_status = array();
$cookie_contents = file_get_contents($cookie_new);

// Now that we have the contents of the
// "cookie jar", we can delete the temporary
// file to save resources.

if(file_exists($cookie_new))
{
unlink($cookie_new);
}

// Split the jar into it's individual cookies
// by using the "\n" delimiter.

$cookie_contents_array = explode("\n",$cookie_contents);

// Cycle through each cookie in
// $cookie_contents_array and if the cookie
// applies to our domain "mysite.com",
// process it. It's hard to imagine a situation
// where a cookie belonging to another domain
// would get into the temporary cookie file yet
// I can't help but check!

for($i = 0; $i < count($cookie_contents_array); $i++)
{
if(strpos($cookie_contents_array[$i],"mysite.com")!==false)
{

// Process the cookie here. Each entry
// will consist of various snippets of
// information, including the path that
// the cookie belongs to, the time to
// expire the cookie, the cookie name
// and the cookie value. Each bit of
// information is separated by the tab
// character "\t".

$cookie_array = explode("\t",$cookie_contents_array[$i]);

// Small branch here for clarity sake,
// the cookie might not have an expire
// time, in which case we simply don't
// include the "expires" parameter in the
// response we send to the client. One
// other point to note that as of PHP
// version something point something, we
// have an optional boolean flag as the
// second parameter to the header
// function. To set all the cookies
// irrespective of whether they have been
// set previously, pass in false. This
// comes highly recommended!

if($cookie_array[4]!=0)
{
header("Set-Cookie: ".$cookie_array[5]."=".$cookie_array[6]."; expires=".date("r",$cookie_array[4])."; path=/",false);
}
else
{
header("Set-Cookie: ".$cookie_array[5]."=".$cookie_array[6]."; path=/",false);
}
}
}

// Depending on the settings in your
// php.ini file, you should also set the
// PHPSESSID value to the current session
// id for use in both your existing login
// system and the forum script as it will
// no doubt be used extensively.

header("Set-Cookie: PHPSESSID=".session_id()."; path=/",false);

// Phew, finito! Login is successful (at
// least in terms of the user having
// provided the correct credentials),
// you won't know whether the login worked
// at this point although, it's fairly safe
// to say that if your code gets inside
// the 'process cookie' logic, you're well
// on your way.

return true;
}
else
{
// User didn't get their username
// and/or password right so login
// is unsuccessful.

return false;
}
}


That's it with the exception of what do you do after a successful login. This might seem like a pretty benign question but the answer can mean the difference between the script not working and it working. As with cookies / browsers &c., basically that whole client side mess (praise reliable server-side code all the way!), you want to give the script the best chance of working and somewhere along the chain, be it PHP, the browser, the actual cookie specification, the following lines of code are somehow sketchy:


header("Set-Cookie:...");
header("Location:...");


In other words, performing a header redirect directly after a "Set-cookie" header might not actually set the cookie. I suppose this kind of makes sense, as the meaning is a bit ambiguous. I.e. On this page, do something, oh no wait, go over here! So, what's the solution? Well, I'm a big fan of the meta refresh for this bit. Basically, after your function returns true, pull in a file which contains a meta refresh header redirecting the user to your "members-only" index page. So the code might look like this:


if(checkLogin($username, $password, $remember))
{
print file_get_contents("meta_refresh.php?url=/members/index.php&delay=0");
exit();
}


meta_refresh.php might look like this:


if(empty($delay)) { $delay = "0"; }
if(empty($url)) { $url = "/"; }
?>









This way, the page where the cookies were sent in the header actually loads, but the user doesn't really see this. This gives your cookies the best chance of actually being interpreted and processed by the browser. All your unsuspecting user sees is the "members-only" index page after the meta refresh. Easy as!

That's it, I hope this article offers a little bit of insight into the faculty of remote login using cURL. It need not apply specifically to phpbb2 or even a forum, it can be used to login to any web based login system where, of course, you are privileged to have the password. Enjoy!

As with all articles on Celtic Productions, this article is protected by international copyright laws. It may be linked to (we are of course most grateful of links to our articles), however, it may never be reproduced without the prior express permission of its owners, Celtic Productions. It's not a case of writing to Sony Music and asking can you lawfully reproduce and distribute their hottest selling CD, if you do actually want to reproduce this article on your website, just ask, you might be surprised ;-)