diff --git README.txt README.txt
index 878e9a3..5c9a4f8 100644
--- README.txt
+++ README.txt
@@ -31,3 +31,9 @@ Installation
   Depending on where and how you installed the phpCAS library, you may need
   to configure the path to CAS.php. The current library version will be
   displayed if the library is found.
+
+
+API Changes Since 6.x-2.x
+=========================
+The hooks hook_auth_name() and hook_auth_filter() were combined and renamed
+to hook_cas_user_alter(). See cas.api.php.
diff --git cas.api.php cas.api.php
new file mode 100644
index 0000000..bb7033a
--- /dev/null
+++ cas.api.php
@@ -0,0 +1,116 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Documentation for CAS API.
+ */
+
+/**
+ * Modify CAS user properties before the user is logged in.
+ *
+ * Allows modules to alter the CAS user name and account creation permissions
+ * after the CAS user name is returned from phpCAS::getUser().
+ *
+ * Modules implementing this hook may wish to alter 'name' if the CAS server
+ * returns user names which contain excess information or are not directly
+ * machine readable. This field is not the Drupal name of the user. Instead, 
+ * this is used to load a Drupal user via the mapping in the {cas_user} table.
+ *
+ * The 'login' parameter controls whether the user is able to login. By
+ * default this will be set to TRUE, but modules may set this flag to FALSE
+ * to deny the user login access. For example, one might want to only allow
+ * login access to members of a certain LDAP group. This verification is in
+ * addition to the standard feature which lets you block users.
+ *
+ * The 'register' parameter controls whether an account should be created if 
+ * the user does not already have a Drupal account. Defaults to the value of
+ * "Should Drupal user accounts be automatically created?" in the CAS module
+ * settings. This setting is ignored if 'login' is set to FALSE.
+ *
+ * If multiple modules implement this hook, the values set by the last module
+ * to execute this hook will be used. Therefore, it is good practice to only
+ * set the 'login' and 'register' flags to FALSE, rather than the output of
+ * a function. This prevents accidentally allowing a user to login when another
+ * module had already denied access.
+ *
+ * @param $cas_user
+ *   An associative array, with the following keys:
+ *   - 'user': The CAS machine-readable user name.
+ *   - 'login': If TRUE, the user will be allowed to login to an existing
+ *     Drupal account.
+ *   - 'register': If TRUE, the user will be allowed to register a Drupal
+ *     account if one does not already exist. If 'login' is FALSE, this
+ *     setting will be ignored.
+ */
+function hook_cas_user_alter(&$cas_user) {
+  // Alter the CAS user name. The CAS server returned a compound name like
+  //   it:johndoe:10.10.1.2:200805064255
+  // and so we extract the actual user name of 'johndoe'.
+  $parts = explode(':', $cas_user['name'], 3);
+  $cas_user['name'] = $parts[1];
+
+  // Allow logins only for users in a certain LDAP group.
+  if (!_ldap_is_member_group($cas_user['name'], 'admins')) {
+    $cas_user['login'] = FALSE;
+  }
+
+  // Allow registrations only for a certain class of users.
+  if (!_ldap_user_has_home_directory($cas_user['name'])) {
+    $cas_user['register'] = FALSE;
+  }
+}
+
+/**
+ * A CAS user has authenticated and the login is about to be finalized.
+ *
+ * This allows modules to react to a CAS user logging in and alter their
+ * account properties. For example, modules may want to synchronize Drupal
+ * user roles or profile information with LDAP properties.
+ *
+ * The 'cas_first_login' flag in $edit will be set to TRUE if the CAS user was
+ * just registered and this is their first login. This is useful if you want
+ * to only synchronize information when an account is created instead of on
+ * every login.
+ *
+ * The 'cas_user' key in $edit contains all information returned from
+ * hook_cas_user_alter().
+ *
+ * The CAS module promises to call user_save() and user_login_finalize() with
+ * this $edit data.
+ *
+ * @param $edit
+ *   An array of values corresponding to the Drupal user to be created.
+ * @param $account
+ *   A Druapl user object.
+ */
+function hook_cas_user_presave(&$edit, $account) {
+  $cas_name = $edit['cas_user']['name'];
+
+  // Look up the user's real name using LDAP.
+  $ldap_connection = ldap_connect('ldap.example.com', 389);
+  $ldap_result = ldap_search($ldap_connection, 'ou=people', 'uid=' . $cas_name, array('cn'), 0, 1);
+  $entries = ldap_get_entries($ldap_connection, $ldap_result);
+  $attributes = $entries[0];
+
+  if (!empty($attributes['cn'])) {
+    $edit['name'] = $attributes['cn'];
+  }
+}
+
+/**
+ * Modify phpCAS authentication properties.
+ *
+ * This is called after phpCAS has been configured with the basic server
+ * properties, but before phpCAS::forceAuthentication() is called.
+ *
+ * Users will generally not need to implement this hook, as most phpCAS
+ * configuration options are already provided in the CAS module UI.
+ *
+ * There are no parameters, instead the module should directly call the
+ * functions in the phpCAS namespace.
+ */
+function hook_cas_phpcas_alter() {
+  // Set a custom server login URL.
+  phpCAS::setServerLoginURL('https://login.example.com/cas/login');
+}
diff --git cas.module cas.module
index 0a651f7..dd586d0 100644
--- cas.module
+++ cas.module
@@ -18,55 +18,6 @@ define('CAS_LOGIN_REDIR_MESSAGE', 'You will be redirected to the secure cas logi
 define('CAS_EXCLUDE', 'services/*'); 
 define('CAS_AUTHMAP_EXTERNAL', 0);  // Use external authmap entries for cas
 define('CAS_AUTHMAP_INTERNAL', 1);  // Use drupal as the internal user. 
- 
-
-/**
- * Invokes hook_auth_transform() in every module.
- *
- * Other modules may need to transform the results of phpCAS::getUser() into a Drupal username
- * (i.e. phpCAS::getUser() is not guaranteed to return the same username that the user typed in
- *       or the Drupal username might be username@cas or something and we need to know it before we filter)
- *
- * We cannot use hook_insert or any user hooks, because they fire way too late.
- * We cannot use module_invoke_all(), because the argument needs to be passed by reference.
- *
- * @param $cas_user
- *   The cas reply string to transform into a drupal username
- */
-function cas_invoke_auth_transform(&$cas_name) {
-  foreach (module_implements('auth_transform') as $module) {
-    $function = $module . '_auth_transform';
-    if (function_exists($function)) {
-      $function('cas', $cas_name);
-    }
-  }
-}
-
-/**
- * Invokes hook_auth_filter() in every module.
- *
- * We cannot use module_invoke_all() for this,
- * because we want to break out as soon as one fails.
- *
- * @param $cas_user
- *   The transformed $cas_name to filter
- *
- * @return
- *   TRUE if no module implementing this hook denied access
- *   FALSE if any module returned FALSE
- */
-function cas_invoke_auth_filter($cas_name) {
-  foreach (module_implements('auth_filter') as $module) {
-    $function = $module . '_auth_filter';
-    if (function_exists($function)) {
- 
-      if (($return = $function('cas', $cas_name)) === FALSE) {
-        return FALSE;
-      }
-    }
-  } 
-  return TRUE;
-}
 
 /**
  * Implementation of hook_init
@@ -110,7 +61,7 @@ function cas_menu_logout_check() {
  *
  */
 function cas_login_check() {
-  global $user, $account;  
+  global $user;
   if ($user->uid) {
     //Don't Login  because we already are
     return; 
@@ -143,7 +94,6 @@ function cas_login_check() {
     
 
     // Variable set
-    $cas_user_register = variable_get('cas_user_register', 1);
     $cas_authmap       = variable_get('cas_authmap', CAS_AUTHMAP_EXTERNAL);
     $server_version    = (string)variable_get('cas_version', '2.0');
     $server_cas_server = (string)variable_get('cas_server', 'sso-cas.univ-rennes1.fr');
@@ -191,6 +141,10 @@ function cas_login_check() {
         break;
     } 
 
+    // Allow other modules to call phpCAS routines. We do not call
+    // drupal_alter() since there are no parameters to pass.
+    module_invoke_all('cas_phpcas_alter');
+
     // We're going to try phpCAS auth test
     if (!$cas_force_login) {
       $logged_in = phpCAS::checkAuthentication(); 
@@ -200,66 +154,57 @@ function cas_login_check() {
       // We're done cause we're not logged in. 
       if (!$logged_in) return; 
       
-    } 
+    }
     else { 
       phpCAS::forceAuthentication();
     }
-    
-    $cas_name = phpCAS::getUser();
-    
-    /*
-     * Invoke hook_auth_transform($op, &$username)
-     *
-     * Allow other modules to change the login name
-     * eg. if phpCAS::getUser() returns a string like it:johndoe:10.10.1.2:200805064255
-     * eg. if your cas users in Drupal need to be johndoe@cas
-     *
-     * Note: this transformation needs to happen before we check for blocked users.
-     */
-    
-    cas_invoke_auth_transform($cas_name);
-
- 
-    /*
-     * Invoke hook_auth_filter($op, &$username)
-     *
-     * Allow other modules to filter out some cas logins
-     * eg. if you want to use cas authentication but only allow SOME people in
-     * eg. if you want to filter out people without LDAP home directories
-     */
-    if (cas_invoke_auth_filter($cas_name) === FALSE) {
- 
-      drupal_set_message(t('The user account %name is not available on this site.', array('%name' => $cas_name)), 'error');
+
+    // Build the cas_user object and allow modules to alter it.
+    $cas_user = array(
+      'name' => phpCAS::getUser(),
+      'login' => TRUE,
+      'register' => variable_get('cas_user_register', TRUE),
+    );
+    drupal_alter('cas_user', $cas_user);
+
+    // Bail out if a module denied login access for this user or unset the user name.
+    if (empty($cas_user['login']) || empty($cas_user['name'])) {
+      // Only set a warning if we forced login.
+      if ($cas_force_login) {
+        drupal_set_message(t('The user account %name is not available on this site.', array('%name' => $cas_name)), 'error');
+      }
       return;
     }
- 
+
+    // Proceed with the login process, using the altered CAS user name.
+    $cas_name = $cas_user['name'];
+
     // blocked user check
-    
+    $blocked = FALSE;
     if (($cas_authmap == CAS_AUTHMAP_INTERNAL) && user_is_blocked($cas_name)) {
       // blocked in user administration
-      drupal_set_message(t('The username %name has been blocked.', array('%name' => $cas_name)), 'error');
-      return;
+      $blocked = 'The username %cas_name has been blocked.';
     }
-
- 
-    // this is because users can change their name. 
-    if (($cas_authmap == CAS_AUTHMAP_EXTERNAL) && _cas_external_user_is_blocked($cas_name)) {
-
-      // blocked in user administration
-      drupal_set_message(t('The username %name has been blocked.', array('%name' => $cas_name)), 'error');
-      return;
+    // this is because users can change their name.
+    // users are external; use cas_user table for associating external users
+    elseif (($cas_authmap == CAS_AUTHMAP_EXTERNAL) && _cas_external_user_is_blocked($cas_name)) {
+        $blocked = 'The username %cas_name has been blocked.';
     }
-
-     
-    if (drupal_is_denied('user', $cas_name)) {
-
-      
-      // denied by access controls
-      drupal_set_message(t('The name %name is a reserved username.', array('%name' => $cas_name)), 'error');
+    // @todo The D7 equivalent here must have been renamed.
+    // elseif (drupal_is_denied('user', $cas_name)) {
+    //   // denied by access controls
+    //   return 'The name %cas_name is a reserved username.';
+    // }
+
+    if ($blocked) {
+      // Only display error messages only if the user intended to log in.
+      if ($cas_force_login) {
+        watchdog('cas', $blocked, array('%cas_name' => $cas_name), WATCHDOG_WARNING);
+        drupal_set_message(t($blocked, array('%cas_name' => $cas_name)), 'error');
+      }
       return;
-    } 
+    }
 
- 
     // try to log into Drupal
     if ($cas_authmap == CAS_AUTHMAP_INTERNAL) {
       // users are coming from Drupal; no need to use the external_load and the authmap
@@ -271,7 +216,7 @@ function cas_login_check() {
       if ($uid) {
         $acct = user_load($uid);
       }
-      if (empty($acct) && variable_get('cas_hijack_user', 0)) {
+      if (empty($acct) && $cas_force_login && variable_get('cas_hijack_user', 0)) {
         $acct = user_load_by_name($cas_name);
         
         if (isset($acct->uid)) {
@@ -285,11 +230,10 @@ function cas_login_check() {
       }
     }
 
-    
     // If we don't have a user register them.
     if (empty($acct) || !$acct->uid ) {
-      if ($cas_user_register == 1) {
-        $user_default = array(
+      if ($cas_user['register']) {
+        $edit = array(
           "name" => $cas_name,
           "pass" => user_password(),
           "init" => $cas_name,
@@ -297,13 +241,13 @@ function cas_login_check() {
           "status" => 1,
           "roles" => $cas_roles,
         );
-        if ($cas_domain) $user_default['mail'] = $cas_name . '@' . $cas_domain;
+        if ($cas_domain) $edit['mail'] = $cas_name . '@' . $cas_domain;
 
         // Set a session variable to denote this the initial login
         $_SESSION['cas_first_login'] = TRUE;
 
         // now save the user and become the new user.
-        $acct = user_save(drupal_anonymous_user(), $user_default);
+        $acct = user_save(drupal_anonymous_user(), $edit);
         if ($cas_authmap == CAS_AUTHMAP_EXTERNAL) {
           db_insert('cas_user')
             ->fields(array(
@@ -313,41 +257,38 @@ function cas_login_check() {
             ->execute();
         }
         watchdog("user", 'new user: %n (CAS)', array('%n' => $acct->name), WATCHDOG_NOTICE, l(t("edit user"), "admin/user/edit/$acct->uid"));
+
+        // Reset $edit for use later, recording that the user was just
+        // registered.
+        $edit = array('cas_first_login' => TRUE);
       }
     }
     
     // final check to make sure we have a good user
     if (!empty($acct) && $acct->uid > 0) {
-     
-    // Save single sign out information
-    if (!empty($_SESSION['cas_ticket']) && variable_get('cas_signout', FALSE)) {
-      _cas_single_sign_out_save_token($acct);
-    }
-      
-    // update the roles and reset the password
-    $roles = $acct->roles;
-    foreach ($cas_roles as $role) {
-      $roles[$role] = $role;
-    }
-    /* Removing password cause it cases problems with phpcas. */ 
-    $user_up = array(
-      "roles" => $roles,
-    );
-    $acct = user_save($acct, $user_up);
-    
-    $edit = array();
-    if (module_exists('persistent_login') && $_SESSION['cas_remember']) {
-      $edit['persistent_login'] = 1;
-    }
-    $user = $acct; 
-    user_login_finalize($edit); 
-    drupal_set_message(t(variable_get('cas_login_message', 'Logged in via CAS as %cas_username.'), array('%cas_username' => $user->name)));
-    if (!empty($edit['persistent_login']) &&  $edit['persistent_login']== 1) {
-      drupal_set_message(t('You will remain logged in on this computer even after you close your browser.'));
-    }
-    // We can't count on the menu because we're changing login states.
-   
-    cas_login_page();
+      // Save single sign out information
+      if (!empty($_SESSION['cas_ticket']) && variable_get('cas_signout', FALSE)) {
+        _cas_single_sign_out_save_token($acct);
+      }
+
+      // Populate $edit with some basic properties.
+      $edit['cas_user'] = $cas_user;
+      $edit['roles'] = $acct->roles + $cas_roles;
+      if (module_exists('persistent_login') && $_SESSION['cas_remember']) {
+        $edit['persistent_login'] = 1;
+      }
+      // Allow other modules to make their own custom changes.
+      cas_user_module_invoke('presave', $edit, $acct);
+
+      // Save the user account and log the user in.
+      $user = user_save($acct, $edit);
+      user_login_finalize($edit);
+      drupal_set_message(t(variable_get('cas_login_message', 'Logged in via CAS as %cas_username.'), array('%cas_username' => $user->name)));
+      if (!empty($edit['persistent_login']) &&  $edit['persistent_login']== 1) {
+        drupal_set_message(t('You will remain logged in on this computer even after you close your browser.'));
+      }
+
+      cas_login_page();
     }
     // if we have a good user
     else {
@@ -355,7 +296,10 @@ function cas_login_check() {
       // tests?   
       //session_destroy();
       $user = drupal_anonymous_user();
-      drupal_set_message(t('No account found for %cas_name.', array('%cas_name' => $cas_name)));
+      // Only display error messages only if the user intended to log in.
+      if ($cas_force_login) {
+        drupal_set_message(t('No account found for %cas_name.', array('%cas_name' => $cas_name)));
+      }
     }
   }
   // End if user is already logged in else
@@ -943,12 +887,28 @@ function _cas_single_sign_out_save_ticket() {
   }
 }
 
-function _cas_external_user_is_blocked($name) {
-  $deny=FALSE; 
-  $result = db_query("SELECT u.name FROM {users} u JOIN {cas_user} c ON u.uid=c.uid WHERE status = 0 AND c.cas_name = LOWER(:uid)", array(':uid' => $name)); 
-  foreach ($result as $record)  {
-    if ($record->name) $deny = TRUE;  
-  }
-  return $deny;
+/**
+ * Determine whether a CAS user is blocked.
+ *
+ * @param $cas_name
+ *   The CAS user name.
+ *
+ * @return
+ *   Boolean TRUE if the user is blocked, FALSE if the user is active.
+ */
+function _cas_external_user_is_blocked($cas_name) {
+  return db_query("SELECT name FROM {users} u JOIN {cas_user} c ON u.uid = c.uid WHERE u.status = 0 AND c.cas_name = :cas_name", array(':cas_name' => $cas_name))->fetchField();
 }
 
+/**
+ * Invokes hook_cas_user_TYPE() in every module.
+ *
+ * We cannot use module_invoke() because the arguments need to be passed by
+ * reference.
+ */
+function cas_user_module_invoke($type, &$edit, $account) {
+  foreach (module_implements('cas_user_' . $type) as $module) {
+    $function = $module . '_cas_user_' . $type;
+    $function($edit, $account);
+  }
+}
