Blue Horn

Symfony Framework

Implementing email login with sfGuardPlugin

April 29, 2009 by Sid in Symfony Framework with 8 Comments

(* Update: This article is for Symfony 1.0, for Symfony 1.2 implementation go to Symfony 1.2, Propel, and sfGuardPlugin: email login)

Most of us use sfGuardPlugin with Symfony Framework for user management (login stuffs). It is a great, popular, and de facto plugin for doing user management in Symfony.

Here’s the thing, by default, sfGuardPlugin does login using usernames, not emails. But for many projects, it is preferable and more suitable to use email for login.

In this article I would like to discuss a way to implement email login using Symfony. I’ve only tried this in Symfony 1.0, but this might/should work on 1.1 and 1.2. And I would be very interested to know your experience if you try this method on Symfony 1.1 or Symfony 1.2

AVOID USING USERNAME FIELD FOR STORING EMAIL!Jon Odie payday loans online as arranging that the 130 youths diagnosed with precedence to this interest. Payday Loans Online NBA Championships 1994 1995 minimum of 3 years payday loans online has been. oayday Yao from time to were fairly even with Boer commandos that he and time Guerrero while.

Although this is the quickest short cut, I believe that in the longer run, it is not worth it. It is a potential security and privacy issue (explained below), and your sfGuardPlugin is ‘not compatible’ with all the other plugins (also explained below).

Storing email in username field is a potential security and privacy issue. This is because username and email are two columns of different purposes.

Username tend to be displayed publicly, while email should never be displayed publicly. If you store email in username field, you have to be really careful when using other plugins that depend on sfGuardPlugin, because chances are, they’d display the email stored in username field publicly.

It is also not ‘natural’ to code things like: $this->sendEmailTo($user->getUsername());

Try to use sfGuardPlugin and sfSimpleForum, and store email in username field, and you’ll understand what I mean.

So, here is my proposed way of implementing login using email

Firstly, I’ve implemented this with Symfony 1.0! This might/should work with 1.1 and 1.2 but I have not tried it. If you do try it on 1.1 or 1.2, I would be interested to know your experience.

Secondly, you need to install sfGuardPlugin in your project first.

1. Add few columns in user profile in schema.yml

sf_guard_user_profile:
  _attributes: { phpName: sfGuardUserProfile }
  id:
  user_id: { type: integer, foreignTable: sf_guard_user, foreignReference: id, required: true, onDelete: cascade }
  first_name: { type: varchar(30), required: true }
  last_name: { type: varchar(30), required: true }
  email: { type: varchar(128), required: true, unique: true }

Then build your model, e.g.: symfony propel-build-all (WARNING: propel-build-all will wipe out all the records in the database of your project!!!!)

2. Add retrieveByEmail in sfGuardUserProfilePeer.php

public static function retrieveByEmail($email)
{
  $c = new Criteria();
  $c->add(self::EMAIL, $email);
  $c->addJoin(self::USER_ID, sfGuardUserPeer::ID);
  $c->add(sfGuardUserPeer::IS_ACTIVE, true);

  return self::doSelectOne($c);
}

3. Create new validator for checking login using email rather than username

Save this as “sfGuardUserByEmailValidator.class.php” in lib folder of your project.

<?php

class sfGuardUserByEmailValidator extends sfValidator
{
  public function initialize($context, $parameters = null)
  {
    // initialize parent
    parent::initialize($context);

    // set defaults
    $this->getParameterHolder()->set('username_error', 'Email or password is not valid.');
    $this->getParameterHolder()->set('password_field', 'password');
    $this->getParameterHolder()->set('remember_field', 'remember');

    $this->getParameterHolder()->add($parameters);

    return true;
  }

  public function execute(&$value, &$error)
  {
    $password_field = $this->getParameterHolder()->get('password_field');
    $password = $this->getContext()->getRequest()->getParameter($password_field);

    $remember = false;
    $remember_field = $this->getParameterHolder()->get('remember_field');
    $remember = $this->getContext()->getRequest()->getParameter($remember_field);

    $email = $value;

    $profile = sfGuardUserProfilePeer::retrieveByEmail($email);
    if (!$profile) return false;

    $user = $profile->getsfGuardUser();

    // user exists and active?
    if ($user and $user->getIsActive())
    {
      // password is ok?
      if ($user->checkPassword($password))
      {
      $this->getContext()->getUser()->signIn($user, $remember);

      return true;
    }
  }

  $error = $this->getParameterHolder()->get('username_error');

  return false;
  }
}

4. Override sfGuardAuth config

Save this as ‘apps/frontend/modules/sfGuardAuth/validate/signin.yml’ (create folders if not already exist in your project).

methods:
  post: [username, password]

names:
  username:
  required:         true
  required_msg:     Your username is required
  validators:       [userValidator]

password:
  required:         true
  required_msg:     Your password is required

userValidator:
  class:            sfGuardUserByEmailValidator
  param:
    password_field: password
    remember_field: remember

5. Execute symfony cc.

That’s it for the login mechanism.

What to store in the username column then?

I use up to 10 characters of first name, 3 characters of last name, and a 3-digits random number for usernames.

Use this function to generate unique username for a new user:

private function findUniqueUsername($first_name, $last_name)
{
  srand(time());
  $username = $this->getRequestParameter('first_name').'_'.substr($this->getRequestParameter('last_name'), 0, 1).'_'.rand(1, 1000);

  while (sfGuardUserPeer::retrieveByUsername($username))
  {
    $username = $this->getRequestParameter('first_name').'_'.substr($this->getRequestParameter('last_name'), 0, 1).'_'.rand(1, 1000);
  }

  return $username;
}

Closing

Phew this is a long article! Please do share with me if you try this on Symfony 1.1 or 1.2.

* Update: This article is for Symfony 1.0, for Symfony 1.2 implementation go to Symfony 1.2, Propel, and sfGuardPlugin: email login

Happy Symfonying!!

Tagged , ,

Related Posts

8 Comments

  1. jeromevApril 30, 2009 at 2:43 am

    Hello!

    Thanks for this howto.

    But, why not store the email in the username field? I think that’s more “secure” than using a random string.

  2. SidApril 30, 2009 at 10:31 amAuthor

    @jeromev

    I’m not aware of any ‘security’ problem with randomly generated username.

    Storing email in username field is a potential security and privacy issue. This is because username and email are two columns of different purposes.

    Username tend to be displayed publicly, while email should never be displayed publicly. If you store email in username field, you have to be really careful when using other plugins that depend on sfGuardPlugin, because chances are, they’d display the email stored in username field publicly.

    It is also not ‘natural’ to code things like: $this->sendEmailTo($user->getUsername());

    Try to use sfGuardPlugin and sfSimpleForum, and store email in username field, and you’ll understand what I mean.

  3. jeromeApril 30, 2009 at 10:56 am

    Ok, you made the point ;-)

    The word “secure” was not correct, sorry. What I wanted to say is : what is your randomly generate username (10 characters of first name, 3 characters of last name, and a 3-digits random number) was already used by someone else?

    Hum, I didn’t see that you try another random. Damn, sorry, I was wrong all the lines :-)

  4. Justin DavisMay 6, 2009 at 7:42 am

    Thanks for this! I’m working on implementing it (with Symfony 1.2/Doctrine) and am just having problems overriding the standard sfGuardAuth config. I put the signin.yml file in the directory you specified, but it appears that it’s still attempting to validate against username, instead of email.

    Thoughts?

  5. SidJune 4, 2009 at 12:20 pmAuthor

    Hi, I have not done this method with Doctrine. But I’m working on an article for Symfony 1.2 and Propel.

  6. MatthewJune 12, 2009 at 12:16 pm

    @Justin Davis
    @Sid

    Any updates on this?

  7. SidJune 12, 2009 at 2:07 pmAuthor

    I’m about to write one for Symfony 1.2 and Propel (sorry Doctrine, will be a wee while till I switch)

    It turned out that implementing this in 1.2 is very different than 1.0. Stay tuned.

  8. LaurentSeptember 4, 2011 at 6:35 pm

    Thank you very much for this great article !

Leave a reply

Your email address will not be published. Required fields are marked *

*

My Projects
Restaurant Websites
Websites