Yet Another Programming Blog
PHP, Drupal, Zend Framework. Done.
PHP, Drupal, Zend Framework. Done.
Thu, 25 Jun, 2009
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:
And, as usual, there are probably 1,000 different ways this functionality could be implemented. My solution used the following philosophy:
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.
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:
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!
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
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...
Thanks for this.
Hi.
I am a new comer to Drupal and wonder if the code above is compatible with Drupal 7.
Thanks again.
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
Nice start!
Many thanks .
Review
Congratulation, to you man, Such a nice post, really interesting, really admire your work, to have some more of it,Thanks.
Thanks Parvez
Thanks Parvez - correction applied. :-)
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'] ) )
good
it is very good, thanks very much.
Thanks sunny
Thanks for that sunny - typo now corrected.
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
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.
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