6 user.module | user_authenticate_finalize(&$edit) |
Finalize the login process. Must be called when logging in a user.
The function records a watchdog message about the new session, saves the login timestamp, calls hook_user op 'login' and generates a new session.
$param $edit This array is passed to hook_user op login.
3 calls to user_authenticate_finalize()
- user_authenticate in modules/
user/ user.module - Try to log in the user locally.
- user_external_login in modules/
user/ user.module - Perform standard Drupal login operations for a user object.
- user_pass_reset in modules/
user/ user.pages.inc - Menu callback; process one time login link and redirects to the user page on success.
File
- modules/
user/ user.module, line 1412 - Enables the user registration and login system.
Code
function user_authenticate_finalize(&$edit) {
global $user;
watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
// Update the user table timestamp noting user has logged in.
// This is also used to invalidate one-time login links.
$user->login = time();
db_query("UPDATE {users} SET login = %d WHERE uid = %d", $user->login, $user->uid);
// Regenerate the session ID to prevent against session fixation attacks.
sess_regenerate();
user_module_invoke('login', $edit, $user);
}
Comments
user_login_finalize() in D7
user_authenticate_finalize() was renamed to user_login_finalize() in Drupal 7.
Drupal 7 Single-Sign On Basics
In Drupal 7, setting up a Single-Sign On scenario involves the core user.module. The example scenario in this comment will review how to setup a module that would interact with the user.module to enable SSO.
First, review the main method that will be invoked in the user.module:
<?php
/**
* Helper function for authentication modules. Either logs in or registers
* the current user, based on username. Either way, the global $user object is
* populated and login tasks are performed.
*/
function user_external_login_register($name, $module) {
?>
To get to the point to invoke this method, we need to get the users... username. We can pull this value from the Drupal login form values. Write a hook for the login form:
<?php
/**
* Implementation of hook_form_alter().
* Change the normal form login form behaviour.
*/
function project_authentication_form_user_login_alter( &$form, $form_state )
{
$form['#validate'] = array( 'user_login_name_validate', 'project_authentication_login_validate', 'user_login_final_validate' );
}
?>
By setting up a hook on the user login form, we are able to override the validate methods. Within the validate method, we can authenticate the username and password on an external server.
<?php
/**
* The project_authentication_auth() function attempts to authenticate a user off the external system using their e-mail address.
*/
function project_authentication_login_validate( $form, &$form_state )
{
$username = $form_state['values']['name'];
$response = project_authentication_check_user($username, $form_state['values']['pass'] );
if ($response != false)
{
user_external_login_register( $username, 'project_authentication' );
$account = user_external_load($username);
$form_state['uid'] = $account->uid;
} // else drop through to the end and return nothing - Drupal will handle the rejection for us
}
?>
There are some peculiar steps going on here, let's talk about this method.
The $username is pulled from the login form input. That username along with the form associated password is validated through the method 'project_authentication_check_user':
<?php
function project_authentication_check_user($email,$password){
try
{
$result = ....<external system authentication code>
if($result->size) {
$info = ....<external system information>
return $info;
}
return false;
}
catch (Exception $e) {
watchdog('prjauth', 'Error %error_message.', array('%error_message' => $e->faultstring), WATCHDOG_NOTICE);
return false;
}
}
?>
Since the username and password passed through the method 'project_authentication_check_user', we can continue in the method 'project_authentication_login_validate' and finally invoke:
<?php
user_external_login_register( $username, 'project_authentication' );
?>
This method will register the username if that username does not yet exist in our system... Yes, we are able to login users into Drupal that don't exist in our system because they exist in the external system. We register them based on the information passed from the 'project_authentication_check_user' method.
Logging in the user is based on the flag 'project_authentication' that was passed into the user_external_login_register method. How is that possible? Do you have access to the Drupal database? Take a look at the AUTHMAP table.
The AUTHMAP table has a field called MODULE. The value 'project_authentication' is stored in the MODULE field. That way, the field AUTHNAME must have an entry that matches the $username value that was passed into the method 'user_external_login_register'. Based on that mapping, the user is logged into Drupal.
Of course, there needs to be some trust between Drupal and the external system. This is outside of this tutorial, but you can either have trust based on tightly coupled systems or use SAML (http://en.wikipedia.org/wiki/Security_Assertion_Markup_Language). It is probably much easier to leverage the openID asset provided by Drupal: http://drupal.org/handbook/modules/openid
There is one last important note to discuss about the method 'project_authentication_login_validate'. We are populating $form_state with the users identifier.
<?php
$account = user_external_load($username);
$form_state['uid'] = $account->uid;
?>
We do this because a part of our login form hook was to involve 'user_login_name_validate'. This is being called so that form submit doesn't get called after validate is completed since the method 'user_external_login_register' has invoked form submit for us. The method 'user_login_name_validate' needs the users identifier to be able to operate, so we populate the form_state when completing the 'user_external_login_register' method.
By the way, the method 'user_external_load' that we leveraged to get the users identifier is still referencing the AUTHMAP table. Based on the username that we are using as part of our SSO experience, we assume it is unique in the AUTHMAP.authname field.
So, that is it. Did you think there was anything more to do? Possibly there is. The roles still need to be dealt with. You can map the external user with a role mapping and manually edit the role as the user registers.
Also, when a user forget's a password, they don't actually ever use the Drupal password since it resides in the external system. That will be in another tutorial coming soon.
Multiple user login submit calls
I arrived at the same conclusions you did on how to implement SSO in Drupal 7 but I noticed an additional piece worth getting some input on. Looking at the logs each login had two entries saying Session opened for...
Digging into it I think that the form calls user_login_submit which is also called by user_external_login_register. I got around it by updating my validation method, project_authentication_login_validate in the example above to take the form by reference and re-set $form['#submit'] to an empty array on success, making the function look like this:
<?php
/**
* The project_authentication_auth() function attempts to authenticate a user off the external system using their e-mail address.
*/
function project_authentication_login_validate( &$form, &$form_state )
{
$username = $form_state['values']['name'];
$response = project_authentication_check_user($username, $form_state['values']['pass'] );
if ($response != false)
{
user_external_login_register( $username, 'project_authentication' );
$account = user_external_load($username);
$form_state['uid'] = $account->uid;
$form['#submit'] = array();
} // else drop through to the end and return nothing - Drupal will handle the rejection for us
}
?>
Have you seen similar results?