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!

<?php
/**
* 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:

<?php
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.

photo

Seamless Cross-platform Login

To make this approach really seamless would be tricky, but I think it would be possible. You would need some combination of the following components:

  • Assuming this is an existing Drupal site and all the pages are simply "nodes" and you want to stop non-logged in users from accessing any of the pages, you would need to go to the user permissions page, find the "Access Content" option under "node module" and uncheck the box in the "anonymous user" column but make sure the box in the "authenticated user" column is ticked. That’s the easy part!
  • You would need to install the Custom Error module, which allows you to display a certain page if somebody tries to view a page that they aren’t allowed to. The key thing here is that it also allows you to run PHP code, so this would be your opportunity to call some code that checks to see if the user has logged into the other system recently and, if they haven't, redirect them.
  • You might also want to make a modification to the other system so that if the person was redirected there from the Drupal site that it redirects them back after they have logged in.
  • The really tricky part is this though: whatever page the external system redirects back to on the Drupal site has to use some of the code in my article to log the user onto the Drupal system (which will set them as an "authenticated user" for that session) and, ideally, redirect them to the page they were originally trying to hit. Not sure how you would store this in such a way that it was easily accessible - maybe a cookie?

So although it is possible, it would be quite complicated to implement seamlessly and requires fairly tricky mods on both systems. Good luck if you attempt it though - it sure would be an interesting assignment!

photo

external login

Hi there,

is offering drupal 6 any solution for external login into drupal? example: I need check on my drupal site, if user is logged in https: based site and then show content....also if user is not logged in https, relocate link to https server for login, if successfull, return back to drupal site and show content..is it possible? thanks for any ideas

photo

Drupal 7 version coming soon

Heh heh... I was wondering how long it would take before somebody asked that question! This post is by far the most popular page on the site - I get hundreds of hits per month from people searching Google for Drupal authentication code and ending up here - so I guess I better get moving and work on the Drupal 7 version soon. :-)

Stay tuned...

photo

Thanks for this.

Hi.

I am a new comer to Drupal and wonder if the code above is compatible with Drupal 7.

Thanks again.

photo

hi

I must say that generally I am really impressed with this blog. After reading your post I can tell you are chuffed about your writing. Keep up the great work and I’ll return for more! Cheers!
welcome to my website carbon fibre parts

photo

Nice start!

Many thanks .

photo

Review

Congratulation, to you man, Such a nice post, really interesting, really admire your work, to have some more of it,Thanks.

photo

Thanks Parvez

Thanks Parvez - correction applied. :-)

photo

Saved me hours, thanks very

Saved me hours, thanks very much.

Slight error in post - missing a closing bracket

if ( externalUserValidPassword( $username, $form_state['values']['pass'] )

should be

if ( externalUserValidPassword( $username, $form_state['values']['pass'] ) )

photo

good

it is very good, thanks very much.

photo

Thanks sunny

Thanks for that sunny - typo now corrected.

photo

Thanks for the example

James,
Thanks a lot for writing this external authentication article. Used parts for it to authenticate against our external web services.

Keep up the good work.

a small typo function name externalUserValidatePassword should be changed to externalUserValidPassword

Thanks

photo

I originally "mapped" (if you

I originally "mapped" (if you could call it that :-) the process using Excel. I'm a bit pressed for time at the moment (discovering the joys of right-to-left web pages with a Hebrew translation that I'm working on!) so I have attached the Excel file and a simple HTML version of it. Hopefully you will find this useful.

photo

Nice start! I'm glad you...

Nice start!
I'm glad you... took the time to map the login process. I had the not so nice prospect of doing that today. I wonder if you have a schematic of that process.
I'll try to find and correct any flaws in the code, as I'm facing an almost identical problem you do.
I will most certainly get back to you!

Many thanks for the heads up!

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.
By submitting this form, you accept the Mollom privacy policy.