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..5b05224 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
@@ -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(); 
@@ -204,37 +158,25 @@ function cas_login_check() {
     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) {
- 
+    // 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'])) {
       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
-    
     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');
@@ -288,8 +230,8 @@ 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 +239,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,6 +255,10 @@ 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);
       }
     }
     
@@ -323,24 +269,16 @@ function cas_login_check() {
     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();
+
+    // 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;
     }
-    $user = $acct; 
-    user_login_finalize($edit); 
+    cas_user_module_invoke('presave', $edit, $acct);
+    $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.'));
@@ -952,3 +890,15 @@ function _cas_external_user_is_blocked($name) {
   return $deny;
 }
 
+/**
+ * Invokes hook_cas_user() 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);
+  }
+}
