Write an Advanced Joomla 1.5 Authentication Plugin

Posted by: gbluma

Tagged in: Programming , Plugins , Joomla

Why Joomla's (or any CMS's) login system alone isn't enough

In the support of any long-lived project, chances are pretty good that you'll need to adapt the project to meet the requirements of another system. One specific area which I want to focus on is authentication-- that is, being able to extend Joomla's standard authentication system to support an alternate user table.

The problem we have is a CMS (Joomla) that needs to integrate with another project. This alternate project has it's own user database and login system which we can't modify. The resulting solution must work independently from the alternate project but also share login details with Joomla.

How to approach this

Option 1: We can extend the database Joomla is using with the extra fields we need and simply tell our alternate project to look at that table. However, in my attempt to futureproof my code I would like to stay away from being dependent on Joomla's table structure for this one.

Option 2: Use Joomla's finely crafted Event system to extend it's login process to check a second, or third, or n-to-the-negative-fifth table for other user details--All without duplication of data, which is bad. (see Don't Repeat Yourself)

Joomla's Event system allows us to create a single php file which can bridge the two systems without editing anything in the core of either system.

Getting your feet wet

Here is a quick example of a short plugin which doesn't do anything. (plgAuth.php)

 * @license    GNU/GPL
*/

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die();

// Make sure we have a JPlugin class to extend from
jimport('joomla.event.plugin');

/**
* Example Authentication Plugin. Based on the example.php plugin in the
* Joomla! Core installation
*
* @package Lab11.Tutorials
* @subpackage Plugins
* @license GNU/GPL
*/
class plgAuthenticationMyAuth extends JPlugin
{
/**
* Constructor
*
* For php4 compatability we must not use the __constructor as a constructor
* for plugins because func_get_args ( void ) returns a copy of all passed
* arguments NOT references. This causes problems with cross-referencing
* necessary for the observer design pattern.
*
* @param object $subject The object to observe
* @since 1.5
*/
function plgAuthenticationMyAuth(& $subject) {
parent::__construct($subject);
}

}

... and our XML file so that Joomla understands the details of our plugin (plgAuth.xml)


LabEleven Alternate Authentication
LabEleven
December 2008
Copyright (C) 2008 Lab Eleven. All rights reserved.

gbluma_at_lab11.com
www.lab11.com
1.5
testing

plgAuth.php


We put these files in joomla/plugins/authentication/

At this point Joomla will see our plugin and initialize it, however it won't do anything yet since our plugin is ONLY structure and no functionality.

Basic Login

Now it's time time to make our plugin work. We add the following code to plgAuth.php (after the constructor).

    /**
* This method should handle any authentication and report back to
* the subject
*
* @access public
* @param array $credentials Array holding the user credentials
* @param array $options Array of extra options
* @param object $response Authentication response object
* @return boolean
* @since 1.5
*/
function onAuthenticate( $credentials, $options, &$response )
{
/*
* Here you would do whatever you need for an authentication routine
* with the credentials
*
* In this example the mixed variable $return would be set to false
* if the authentication routine fails or an integer userid of the
* authenticated user if the routine passes
*/

// grab user/pass from $credentials passed in from Joomla's login form.
$username = mysql_real_escape_string($credentials['username']);
$password = mysql_real_escape_string( md5($credentials['password']) );

// build our query to retrieve a user account
$sql = "SELECT * FROM `alternate_users` WHERE `username`='$username' ".
"and `password`='$password' LIMIT 1";
// run the query
$result = mysql_query( $sql );
if (!$result) {
// ... for extended debugging info, uncomment the following line.
//die("No result for user.". $sql . " Errors: " . mysql_error()) ;

$response->status = JAUTHENTICATE_STATUS_FAILURE;
$response->error_message = "Unable to query database.";
return false;
}

// get our user data from
$user = mysql_fetch_assoc($result);

if (empty($user)) {
$response->status = JAUTHENTICATE_STATUS_FAILURE;
$response->error_message = "Unable to find user";
return false;
}

// once we have our user, pass some data to joomla
$response->email = $user['email'];
$response->fullname = $user['contact'];
$response->status = JAUTHENTICATE_STATUS_SUCCESS;

// return successfully
return true;
}

All we need to do now is make sure Joomla is using our plugin by going into the admin site and enabling it. ( Extensions -> Plugin Manager -> Click the "X" by "Authentication - plgAuth" )

Now we can test our login from the front-end and Joomla will dynamically create a user of it's own using the details we provided with $response->email and $response->fullname

Bridging the gap

What we have right now is a way for users to login to Joomla without first creating a user manually. But this really only an illusion of a bridge. In reality the connection isn't there because there is no reference between one login system to another. So lets go about creating that...

Insert this code after our onAuthenticate() function but inside our plugin class.

    /**
* This method alters our alternate table to reference our
*
* @access public
* @param array $columns An array of the columns in the user table.
* @param object $id A unique identifier for this user.
* @return none
* @since 1.5
*/
function onAfterStoreUser( $columns, $id )
{
$user = mysql_real_escape_string($columns['username']);
$user_id = mysql_real_escape_string($columns['id']);

 $sql = "UPDATE `l11m_cgl_users` SET joomla_uid = '" . $user_id . "'"
. " WHERE username='" . $user . "'";
mysql_query($sql) or die("failed to update user. " . mysql_error());
}

Now we have a plugin that will automatically synchronize joomla with the alternate system when a new user is created. There are more events which might also be useful to you:

  • onBeforeStoreUser--do preparation before allow a user to be created
  • onAuthenticationFailure--flag someone as a hacker after x attempts?
  • onBeforeDeleteUser--clean up extended data
  • onAfterDeleteUser--verify / log deletion

There are a lot of cool things that you can do with an event based login system, and I'm sure that I'm only scratching the surface.

Reference: