Drupal 6 External Authentication - Sample Code

UPDATE: November 2, 2011: If you are looking for Drupal 7 external authentication sample code, you can now find it here.

Despite some (arguably justified) grumblings about the lack of themes available for Drupal, I still think its a winner, particularly when it comes to coding your own modules.

Last week I was in the midst of a Drupal 5 to Drupal 6 upgrade for my current employer when I realised that the custom external authentication module I had written for our Drupal 5 site was not going to work on our brand spanking new Drupal 6 site.

A quick search on drupal.org revealed that the authentication process had changed fairly significantly.

So I set about re-writing the module.

Although there are many different ways in which you can integrate two systems using the external authentication functionality, my specific requirements were:

  • to allow users to log in using their e-mail address and password from another system
  • to prevent users from accessing the “change password” functionality (changing passwords was an existing function on the other system)
  • to assign those users a certain role so that specific content could be made available to those users.

And, as usual, there are probably 1,000 different ways this functionality could be implemented. My solution used the following philosophy:

  • change as little of the standard Drupal login process as possible
  • write as little code as possible, utilizing Drupal helper functions where possible.

So, without further ado, I present the sample code. Please note that this is actually a cut-down version of the original, intended as an example only. I haven’t actually run this snippet, so if you do find the odd syntax error, please feel free to add a comment or drop me a line. Thanks!

/**
 * Implementation of hook_help().
 */
function custom_auth_help( $path, $arg )
{
    switch ( $path )
    {
        case ’admin/help#custom_auth’:
        {
            return( ’<p>' . t('This module allows users who login with e-mail addresses to authenticate off an external system.') . '</p>' );
        }
    }
}

 /**
   * Implementation of hook_form_alter().
   *
   * Change the normal form login form behaviour.
   */
function custom_auth_form_user_login_alter( &$form, $form_state )
{
    unset($form['links']);
    $form['#validate'] = array(  'user_login_name_validate', 'custom_auth_login_validate', 'user_login_final_validate' );
}

function custom_auth_form_user_login_block_alter( &$form, $form_state )
{
  return custom_auth_form_user_login_alter( $form, $form_state );
}

/**
 * The custom_auth_auth() function attempts to authenticate a user off the external system using their e-mail address.
 */
function custom_auth_login_validate( $form, &$form_state )
{
    $username = $form_state['values']['name'];

    // In our case we're assuming that any username with an '@' sign is an e-mail address, hence we're going to check the credentials against our external system.
    if ( strpos( $username, '@' ) !== false )
    {
        // Look for user in external database
        if ( externalUserExists( $username ))
        {
            // Looks like we found them - now we need to check if the password is correct
            if ( externalUserValidPassword( $username, $form_state['values']['pass'] ))
            {
                user_external_login_register( $username, 'custom_auth' );
                user_authenticate_finalize( $form_state['values'] );
            } // else drop through to the end and return nothing - Drupal will handle the rejection for us
        }
    }
    else
    {
        // Username is not an e-mail address, so use standard Drupal authentication function
        user_login_authenticate_validate( $form, &$form_state );
    }
}

/**
 * The custom_auth_user() function gets called by Drupal after a new user has been added. If the e-mail address has
 * already been set then we don't want to overwrite it, as the user is probably being added manually. Thankfully
 * the only time a user can be added without the e-mail being set is when custom_auth_auth() gets run for a first-time
 * user, at which point a user is inserted without an e-mail address. That is the case we're dealing with in this
 * function.
 */
function custom_auth_user( $op, &$edit, &$account, $category = null )
{
    switch( $op )
    {
        case( 'insert' ): // This hook is called during the registration process, AFTER the new user has been added to the users table but BEFORE the roles are written to the users_roles table
        {
            if ( empty( $account->mail ))
            {
                db_query("UPDATE {users} SET mail = '%s' WHERE uid = %d", $account->name, $account->uid);
            }
            
            // Note: you can do other stuff here, like set the password to be the md5 hash of the remote password. This might be handy if you wanted to allow people to log on when the external system is unavailable, but, of course, it introduces the hassle of keeping the passwords in sync.

            // This is where we set that additional role to indicate that the user is authenticated externally. Note that EXTERNAL_AUTH_RID is undefined in this sample code but would normally be set to whatever Role ID is in the database. (So that is, create the new role, do a query to find the RID for that role and set EXTERNAL_AUTH_RID to that RID. Or just hard code it in the following line.)
            $edit['roles'][EXTERNAL_AUTH_RID] = 'external_auth';
            return;
        }

        case( 'update' ): // This hook is called BEFORE the record in the database (or $account) has been updated
        {
            if ( strpos( $account->name, '@' ) !== false )
            {
                // If the user is identified by their e-mail address and they are trying to change their e-mail address, don't let them.
                if ( strcmp( $account->mail, $edit['mail'] ))
                {
                    unset( $edit['mail']);
                    drupal_set_message( 'Sorry, users who use their e-mail address to login cannot change their e-mail address.', 'error' );
                }

                // If the user is identified by their e-mail address and they are trying to change their password, don't let them.
                if ( $edit['pass'] != '' )
                {
                    unset( $edit['pass']);
                    drupal_set_message( 'Please access the external system to change your password.', 'error' );
                }
            }

            return;
        }
    }
}

/*
 * Helper functions that you will need to implement
 */
function externalUserExists( $username )
{
    return true;
}

function externalUserValidPassword( $username, $password )
{
    return true;
}

To run this code, you will need to save it as custom_auth.module and create a file called custom-auth.info that contains the following:

name = Custom Auth
description = Authenticate users against an external system.
package = Custom
core = "6.x"
version = "6.x-1.0"

Once you have both those files, pop them into a sub-directory called “custom auth” under the “modules” directory then enable the module. Keep in mind that if you’ve implemented the sample code verbatim, ie. without changing the two helper functions at the end, ANYBODY will be able to gain instant access to your site as an authenticated user by logging in with ANY e-mail address. You have been warned!

My intention is to add a page to the Drupal site about writing custom/external authentication modules, as I actually spent several hours trawling through the Drupal 6 code and mapping out the entire process flow in order to write my own module. So if there are any specific use cases that you are interested in learning about, drop me a line or, better still, add a comment below and I’ll do my best to respond.

Yet Another Programming Blog

Where James Gordon rambles about PHP and web development in general.

Find me on Twitter Find me on Stack Exchange Find me on Github Subscribe