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

/**
 * 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,
    // hence 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.

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