Drupal 7 External Authentication - Sample Code

Having received quite a number of requests through my contact page for Drupal 7 External Authentication Sample Code, today I finally found the time to churn something out.

The Drupal 7 API has changed fairly significantly, with major changes to the database API, hook_user() API, etc. However, the documentation is awesome and the various threads and other contributions on drupal.org make it fairly easy to figure out the changes.

As with my Drupal 6 External Authentication Sample Code, I have once again kept things simple by implementing a scheme whereby any user identified by an email address is assumed to be subject to external authentication, whilst all other users will be subject to Drupal's internal (password) authentication.

To get this code working you will need to:

  1. Add a folder called "extauth" underneath your "sites/all/modules" folder
  2. Place a copy of the two files below in that folder
  3. Use the admin interface to create a new role called "external user"
  4. Go to the Modules administration area and activate the "extauth" module.

If you do all this on a clean installation of Drupal 7, the new role you created will end up with a role ID of 3. If you had other roles already in your database, take note of the role ID of the new role and change the definition of EXTERNAL_AUTH_RID in the code.

Actually, it just occurred to me that the creation of the "external user" role is superfluous in the code below. However, in a real application you will more than likely want to give externally authenticated users a different set of permissions than other users, so assigning them a specific role will be very helpful.

Anyway, enough of my babbling. Here's the code!

extauth.module

<?php
/**
* Implement hook_help() to display a small help message if somebody clicks the "Help" link on the modules list.
*/
function extauth_help( $path, $arg )
{
    switch (
$path )
    {
        case
'admin/help#extauth':
        {
            return(
'<p>' . t('This module allows users who login with e-mail addresses to authenticate off an external system.') . '</p>' );
        }
    }
}

/**
   * Implement hook_form_alter() to change the behaviour of the login form.
   *
   * Login validators are set in the user_login_default_validators() function in user.module.
   * They are normally set to array('user_login_name_validate',
   * 'user_login_authenticate_validate', 'user_login_final_validate').
   * We simply replace 'user_login_authenticate_validate' with 'extauth_login_validate'.
   */
function extauth_form_user_login_alter( &$form, $form_state )
{
    unset(
$form['links']);
   
$form['#validate'] = array( 'user_login_name_validate', 'extauth_login_validate', 'user_login_final_validate' );
}

function
extauth_form_user_login_block_alter( &$form, $form_state )
{
    return
extauth_form_user_login_alter( $form, $form_state );
}

/**
* Implement hook_user_profile_form_alter() to disable the ability to change email address and
* password for externally authenticated users.
*/
function extauth_form_user_profile_form_alter( &$form, $form_state )
{
    if (
strpos( $form['#user']->name, '@' ) !== false )
    {
       
$form['account']['name']['#disabled'] = TRUE;
       
$form['account']['name']['#description'] = t('The username for this account cannot be changed');
       
$form['account']['mail']['#disabled'] = TRUE;
       
$form['account']['mail']['#description'] = t('This e-mail address for this account cannot be changed.');
       
$form['account']['current_pass']['#disabled'] = TRUE;
       
$form['account']['current_pass']['#description'] = t('Neither the email address or password for this account can be changed.');
       
$form['account']['pass']['#disabled'] = TRUE;
       
$form['account']['pass']['#description'] = t('The password for this account cannot be changed.');
    }
}

/**
* The extauth_login_validate() function attempts to authenticate a user off the external system
* using their e-mail address.
*/
function extauth_login_validate( $form, &$form_state )
{
    global
$user;

   
$username = $form_state['values']['name'];

   
// In our case we're assuming that any username with an '@' sign is an e-mail address,
    // henc we're going to check the credentials against our external system.
   
if ( strpos( $username, '@' ) !== false )
    {
       
// Looks like we found them - now we need to check if the password is correct
       
if ( validateExternalUser( $username, $form_state['values']['pass'] ))
        {
           
user_external_login_register( $username, 'extauth' );
           
// I wish we didn't have to do this, but I couldn't find any other way to get the
            // uid at this point
           
$form_state['uid'] = $user->uid;
        }
// else drop through to the end and return nothing - Drupal will handle the rejection
   
}
    else
    {
       
// Username is not an e-mail address, so use standard Drupal authentication function
       
user_login_authenticate_validate( $form, $form_state );
    }
}

/**
* The extauth_user_insert() 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 an external user gets authenticated for the first time, at which
* point a user is inserted into the database without an e-mail address, which is the case we
* will deal with in this function.
*/
define( 'EXTERNAL_AUTH_RID', 3 );

function
extauth_user_insert( &$edit, &$account, $category = null )
{
   
// Remember: this function gets called whenever a new user is added, not just when a new
    // user is being added as a result of them being externally authenticated. So we need to
    // avoid running the following checks if the user is being added by some other means (eg.
    // manually by the administrator). In this simple example we're assuming that any user ID
    // that is an email address is externally authenticated. However, there are possibly
    // better ways to do this, such as look up the authmaps table and see if there is a row
    // for this user where module is 'extauth'.
   
if ( strpos( $account->name, '@' ) !== false )
    {
       
// 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_update( 'users' )->fields( array( 'mail' => $account->name ))
                                ->
condition( 'uid', $account->uid, '=' )
                                ->
execute();
        }

       
// 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 defined as 3 in this sample code but you
        // should set it to whatever Role ID is appropriate in your case, eg. create the new
        // role, do a query to find the RID for that role and set EXTERNAL_AUTH_RID to that RID.
       
$account->roles[EXTERNAL_AUTH_RID] = 'external user';
    }
}

/**
* This is the helper function that you will need to modify in order to invoke your external
* authentication mechanism.
*/
function validateExternalUser( $username, $password )
{
    return
true;
}
?>

extauth.info

name = External Authentication
description = Authenticate users against an external service.
package = Authentication
core = "7.x"
version = "7.x-1.0"

I get a TON if hits on this site from people looking for Drupal 7 external authentication code, so hopefully this article will address that need for more than a few people. If you spot any issues with the code, please feel free to drop me a line.

photo

Still puzzled...

This is just a trivial point, but to be super-accurate with your change in the strpos() logic you should use '===' instead of '=='. Not that you're ever going to have a user with an '@' sign as the first character, but if you did, strpos() would return the offset of 0, and '== false' is equivalent to '== 0'.

But back to the real problem at hand, it sounds like you're on the right track regarding the databases - it sounds like there is an issue somewhere in there. That unique key violation is still the key to solving the issue. You need to run it again, work out which user record was involved in the unique key violation and then figure out logically why that happened.

Good luck!

photo

im using == false cause i

im using == false cause i want to use an exist database of a college to authenticate, so i decided to use @ in my drupal accounts, my validate dont work even if I just use return true;
my original validate dont mess with the drupal db.
Maybe the problem is caused cause I deleted some users from de db, only in the users and users_role (?) table.

Im updating the mail, i checked in the db.

photo

Puzzling...

Do you have code in validateExternalUser() that updates the "users" table in the Drupal database? I'm assuming you do, because the DB error suggests that you are either trying to insert a record into the users table with a "name" that already exists in the database, or you are trying to update the "name" field of an existing record to a "name" already exists in the database. It is hard to say exactly where the error is without seeing more of the code in your validateExternalUser() function, but my advice would be to double-check the code and make sure you're actually setting the "mail" field, as illustrated in your comment, rather than accidentally setting the "name" field.

Also, I am curious as to why you would change the "!==" comparison to "==". Assuming you are trying to achieve what the original code sets out to do, then you should invoke the custom authentication processing when you find an '@' sign in the username, which is accomplished the way the original code was written. The fact that you changed it to '== false', which is not the same as '=== false', suggests a little bit of confusion, perhaps, about the way strpos() works.

photo

Hi, i tried to use accounts

Hi, i tried to use accounts with @ for external authentication, i changed all the if ... !== false to if ... == false and the email thing to db_update( 'users' )->fields( array( 'mail' => 'none@example.com' ))
My authentication is working fine, i create a user fine too, but i cant login with this user, i checked the BD too and its also fine.

here the error.

Erro
Menssagem de erro

Notice: file_get_contents(): Content-type not specified assuming application/x-www-form-urlencoded em validateExternalUser() (linha 153 de /var/www/dteste/sites/all/modules/extauth/extauth.module).
PDOException: SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "users_name_key": INSERT INTO users (uid, name, pass, created, access, status, init) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6); Array ( ) em drupal_write_record() (linha 6861 de /var/www/dteste/includes/common.inc).

photo

Checking authmap table

As mentioned in one of the comments, an alternative way to check if a user was externally authenticated is to check the authmap table.

To address the issue mentioned in the previous comment, try replacing the extauth_form_user_profile_form_alter() function with this version:

<?php
function extauth_form_user_profile_form_alter( &$form, $form_state )
{
   
$records = db_query( 'SELECT module FROM {authmap} WHERE authname = :name',
                         array(
':name' => $form['#user']->name ))->fetchAll();

//    if ( strpos( $form['#user']->name, '@' ) !== false )
   
if ( count( $records ) > 0 && $records[0]->module == 'extauth' )
    {
       
$form['account']['name']['#disabled'] = TRUE;
       
$form['account']['name']['#description'] = t('The username for this account cannot be changed');
       
$form['account']['mail']['#disabled'] = TRUE;
       
$form['account']['mail']['#description'] = t('This e-mail address for this account cannot be changed.');
       
$form['account']['current_pass']['#disabled'] = TRUE;
       
$form['account']['current_pass']['#description'] = t('Neither the email address or password for this account can be changed.');
       
$form['account']['pass']['#disabled'] = TRUE;
       
$form['account']['pass']['#description'] = t('The password for this account cannot be changed.');
    }
}
?>

Please note that if you implement this change, you should probably update the other instances in the code where we are currently just checking for the existence of an '@' sign in the username. I will leave this as an exercise for the reader. :-)

Another alternative is to check if the user has the 'external user' role, but I wouldn't recommend it simply because if an admin accidentally accidentally adds or removes this role from the wrong user, things might go a bit awry for that user. Using the authmap table is, IMO, a more robust alternative.

photo

Thank you

This will help me a lot.
The only problem I find is that I can login using an e-mail that already exists in a Drupal user, drupal register the user but i cant delete/edit him, he still saying "e-mail is already in use" or something like that.

But it still works really fine.
Thank you!

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.