doctrine, howto, php, symfony → Using sfDoctrineGuardUser’s external authentication
sfDoctrineGuardUser has a neat ability to allow you to use an external (the examples are all for LDAP, but would work for anything) authentication process for authenticating your users.
This is really helpful, assuming that you don’t mind manually adding all users in the LDAP directory to the sf_guard_user table. This is because the way that the plugin determines if you are authenticated in sfGuardValidatorUser::doClean contains this
// user exists?
if ($username &&
$user = Doctrine::getTable('sfGuardUser')->retrieveByUsername($username))
{
// password is ok?
if ($user->getIsActive() && $user->checkPassword($password))
{
return array_merge($values, array('user' => $user));
}
}
The user is retrieved from the database before anything else happens.
Unfortunately, that means that if the username doesn’t exist in the local database, you won’t ever get to the point where it will check against your fancy external authentication code.
This post is about how to resolve that issue.
Firstly, you need to change the form that is used for rendering the sgGuardAuth signin action. Do this by adding the following line to your app.yml under “all”
sf_guard_plugin_signin_form: myAuthSigninForm
as described on the symfony docs
Then you need to create that file (I put it in /apps/frontend/lib) and overwrite configure so that you can set it to use your custom validator
class sfGuardCustomFormSignin extends sfGuardFormSignin
{
public function configure(){
parent::configure();
$this->validatorSchema->setPostValidator(new sfGuardValidatorCustomUser());
}
}
This will allow you to use the custom validator that you are going to put in a library directory somewhere.
I put it here:
apps/frontend/modules/sfGuardAuth/lib/sfGuardValidatorCustomUser.class.php
This validator should extend sfGuardValidatorUser and you will need to overwrite doClean. In the example below the highlighted section is the code that we added to the doClean method.
class sfGuardValidatorCustomUser Extends sfGuardValidatorUser
{
protected function doClean($values)
{
$username = isset($values[$this->getOption('username_field')]) ? $values[$this->getOption('username_field')] : '';
$password = isset($values[$this->getOption('password_field')]) ? $values[$this->getOption('password_field')] : '';
//This is the authentication code that you want to replace it with
if($user = myAuth::authenticate($username, $password)) {
return array_merge($values, array('user' => $user));
}
if ($this->getOption('throw_global_error'))
{
throw new sfValidatorError($this, 'invalid');
}
throw new sfValidatorErrorSchema($this,
array($this->getOption('username_field') =>
new sfValidatorError($this, 'invalid')));
}
}
Then all you need to do is to build out your custom authentication mechanism.
I have mine creating or updating the user if it doesn’t exist, because that makes it easy to keep sfDoctrineGuardUser happy.
The actual authentication mechanism exists in another class altogether.
class svExternalAuth
{
public static function authenticate($username, $password){
if( svCustomAuth::authenticate($username, $password) ) {
return self::cleanUser($username, $password);
}
}
//create and or update
protected static function cleanUser($username, $password){
$user = self::getUser($username);
$user->setPassword($password);
$user->setIsActive(true);
//set other user aspects
$user->save();
return $user;
}
protected static function getUser($username){
if($user = Doctrine::getTable('sfGuardUser')->
retrieveByUsername($username)) {
//user exists, return it
return $user;
} else {
//none found, create it.
$user = new sfGuardUser();
$user->setUsername($username);
return $user;
}
}
}
