Home > Symfony Framework > Implementing email login with sfGuardPlugin

Implementing email login with sfGuardPlugin

April 29th, 2009

(* 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!

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!!

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

Symfony Framework , ,

  1. jeromev
    April 30th, 2009 at 02:43 | #1

    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. Sid
    April 30th, 2009 at 10:31 | #2

    @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. jerome
    April 30th, 2009 at 10:56 | #3

    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 Davis
    May 6th, 2009 at 07:42 | #4

    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. Sid
    June 4th, 2009 at 12:20 | #5

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

  6. Matthew
    June 12th, 2009 at 12:16 | #6

    @Justin Davis
    @Sid

    Any updates on this?

  7. Sid
    June 12th, 2009 at 14:07 | #7

    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.

  1. No trackbacks yet.
Subscribe to comments feed