Idea for auth

What do you think about changing the instances of ‘username’ in the auth component to something more generic like ‘account’. Username just seems so specific. like i use my username ‘jayrulez’ to log on to this forum, but not all applications use username for authentication but email, phone #s etc… using account wouldn’t be as specific as username as account can be represented by id, username, email and much more. How about this for Yii?

In Web3CMS we thought about we same, and created a way to log in with either "Username" or "Email" or "Username or email". This is configured in params.php with


// which field to log user in with. possible values: username/email/_any_

    // reset this on any page with MParams::setUserLoginWithField('username');

    'userLoginWithField'=>'username', //'username'

Then in login view we add activeHiddenField($form,‘loginWithField’) to understand what is the account field, and activeTextField($form,$form->getLoginWithField()) to display the account field itself.

Then in UserLoginModel we read the account field and pass it to _CUserIdentity component, which is validating user input agains User db-table according to the account field.

Web3CMS is open source, feel free to download it and try (link is in the bottom).

Listing of user login view:


<?php MParams::setPageLabel(Yii::t('page','Login')); ?>

<?php MUserFlash::setTopError(_CHtml::errorSummary($form)); ?>

<?php MUserFlash::setSidebarInfo(Yii::t('feedback','Hint: You may login with <tt>demo/demo</tt> or <tt>admin/admin</tt>.')); ?>

<?php $this->widget('application.components.WContentHeader',array(

    'breadcrumbs'=>array(

        array(

            'url'=>CHtml::normalizeUrl(array($this->getAction()->getId())),

            'active'=>true

        ),

    ),

)); ?>

<div class="w3-main-form-wrapper ui-widget-content ui-corner-all">


<?php echo _CHtml::beginForm('','post',array('class'=>'w3-main-form'))."\n"; ?>

<?php echo _CHtml::activeHiddenField($form,'loginWithField')."\n"; ?>


<div class="w3-form-row w3-first">

  <div class="w3-form-row-label"><?php echo _CHtml::activeLabelEx($form,$form->getLoginWithField()); ?></div>

  <div class="w3-form-row-input">

    <?php echo _CHtml::activeTextField($form,$form->getLoginWithField(),array('class'=>'w3-input-text ui-widget-content ui-corner-all'))."\n"; ?>

  </div>

  <div class="clear">&nbsp;</div>

</div>

<div class="w3-form-row">

  <div class="w3-form-row-label"><?php echo _CHtml::activeLabelEx($form,'password'); ?></div>

  <div class="w3-form-row-input">

    <?php echo _CHtml::activePasswordField($form,'password',array('class'=>'w3-input-text ui-widget-content ui-corner-all','maxlength'=>64))."\n"; ?>

  </div>

  <div class="clear">&nbsp;</div>

</div>

<?php if(Yii::app()->user->allowAutoLogin): ?>

<div class="w3-form-row">

  <div class="w3-form-row-label">&nbsp;</div>

  <div class="w3-form-row-input">

    <div class="w3-form-row-text">

      <?php echo _CHtml::activeCheckBox($form,'rememberMe')."\n"; ?>

      <?php echo _CHtml::activeLabelEx($form,'rememberMe')."\n"; ?>

    </div>

  </div>

  <div class="clear">&nbsp;</div>

</div>

<?php endif; ?>

<div class="w3-form-row">

  <div class="w3-form-row-label">&nbsp;</div>

  <div class="w3-form-row-input">

    <?php echo _CHtml::submitButton(Yii::t('link','Log in'),array('class'=>'w3-input-button ui-button ui-state-default ui-corner-all'))."\n"; ?>

  </div>

  <div class="clear">&nbsp;</div>

</div>


<?php echo _CHtml::endForm(); ?>


</div><!-- w3-main-form-wrapper -->


<?php MClientScript::registerScript('focusOnFormFirstItem'); ?>

<?php MClientScript::registerScript('w3FormButton'); ?>

Listing of UserLoginForm model:


<?php


/**

 * UserLoginForm class.

 * UserLoginForm is the data structure for keeping

 * user login form data. It is used by the 'login' action of 'UserController'.

 */

class UserLoginForm extends CFormModel

{

    public $email;

    public $password;

    public $rememberMe;

    public $username;

    public $usernameOrEmail;

    public static $loginWithField;


    public function __construct($attributes=array(),$scenario='')

    {

        parent::__construct($attributes,$scenario);

        self::$loginWithField=$this->getLoginWithField();

    }


    /**

     * Declares the validation rules.

     * The rules state that username and password are required,

     * and password needs to be authenticated.

     */

    public function rules()

    {

        return array(

            // username or email or usernameOrEmail is required

            array(self::getLoggingWithField(), 'required'),

            // password is required

            array('password', 'required'),

            // password needs to be authenticated

            array('password', 'authenticate'),

        );

    }


    /**

     * Declares the attribute labels.

     * If an attribute is not delcared here, it will use the default label

     * generation algorithm to get its label.

     */

    public function attributeLabels()

    {

        return array(

            'rememberMe'=>Yii::t('feedback','Remember my member account on this computer'),

            'password'=>Yii::t('t','Password'),

            'username'=>Yii::t('t','Username'),

            'usernameOrEmail'=>Yii::t('t','Username or email'),

        );

    }


    /**

     * Authenticates the password.

     * This is the 'authenticate' validator as declared in rules().

     */

    public function authenticate($attribute,$params)

    {

        if(!$this->hasErrors())  // we only want to authenticate when no input errors

        {

            $identity=new _CUserIdentity($this->{self::getLoggingWithField()},$this->password);

            $identity->authenticate();

            switch($identity->errorCode)

            {

                case _CUserIdentity::ERROR_NONE:

                    // if user is already logged in

                    if(!Yii::app()->user->isGuest)

                    {

                        // log user out from the current account. i want to sleep well, do you? <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/wink.gif' class='bbc_emoticon' alt=';)' />

                        Yii::app()->user->logout();

                        if(!Yii::app()->getSession()->getIsStarted())

                            // restore http session. this is necessary for login

                            Yii::app()->getSession()->open();

                    }

                    // remember for 30 days. makes sence only if auto-login is allowed

                    $duration=(Yii::app()->user->allowAutoLogin && $this->rememberMe) ? 3600*24*30 : 0;

                    // log user in and save in session all appended data

                    Yii::app()->user->login($identity,$duration);

                    // set user preferences (for welcome message, and so on)

                    if(isset(Yii::app()->user->interface) && !empty(Yii::app()->user->interface))

                        // set user preferred interface

                        W3::setInterface(Yii::app()->user->interface);

                    if(isset(Yii::app()->user->language) && !empty(Yii::app()->user->language))

                        // set user preferred language

                        W3::setLanguage(Yii::app()->user->language);

                    break;

                case _CUserIdentity::ERROR_USERNAME_INVALID:

                    if(self::getLoggingWithField()==='username')

                        $this->addError('username',Yii::t('t','Username is incorrect.'));

                    else if(self::getLoggingWithField()==='email')

                        $this->addError('email',Yii::t('t','Email is incorrect.'));

                    else if(self::getLoggingWithField()==='usernameOrEmail')

                        $this->addError('usernameOrEmail',Yii::t('t','Username or email is incorrect.'));

                    break;

                case _CUserIdentity::ERROR_ACCOUNT_IS_INACTIVE:

                    // set the error message

                    MUserFlash::setTopError(Yii::t('feedback',

                        'We are sorry, but your member account is marked as "inactive". Inactive member accounts are temporarely inaccessible. {contactLink}.',

                        array('{contactLink}'=>CHtml::link(Yii::t('link','Contact us'),array('site/contact')))

                    ));

                    // add to username (first field in the login form) error css class

                    // and make the validate() to fail

                    $attribute=self::getLoggingWithField();

                    $attribute!=='username' && $attribute!=='email' && $attribute!=='usernameOrEmail' && ($attribute='username');

                    $this->addError($attribute,'');

                    break;

                case _CUserIdentity::ERROR_IS_NOT_ADMINISTRATOR:

                    // set the error message

                    MUserFlash::setTopError(Yii::t('feedback',

                        'We are sorry, but your access type is {accessType}. Required access type: {requiredAccessType}.',

                        array(

                            '{accessType}'=>Yii::app()->controller->var->userAccessType,

                            '{requiredAccessType}'=>Yii::t('t',User::ADMINISTRATOR_T)

                        )

                    ));

                    unset(Yii::app()->controller->var->userAccessType); // we do not need this any more

                    // add to username (first field in the login form) error css class

                    // and make the validate() to fail

                    $attribute=self::getLoggingWithField();

                    $attribute!=='username' && $attribute!=='email' && $attribute!=='usernameOrEmail' && ($attribute='username');

                    $this->addError($attribute,'');

                    break;

                case _CUserIdentity::ERROR_PASSWORD_INVALID:

                default:

                    $this->addError('password',Yii::t('t','Password is incorrect.'));

                    break;

            }

        }

    }


    /**

    * Which field to log user in with

    * 

    * @return string

    */

    public function getLoginWithField()

    {

        $array=array(

            'username'=>'username',

            'email'=>'email',

            '_any_'=>'usernameOrEmail'

        );

        return $array[MParams::getUserLoginWithField()];

    }


    /**

    * Which field is user now trying to login with

    * If trying to login with username, but later userLoginWithField param

    * was changed to email, still username should be required, not email, right?

    * 

    * @return string

    */

    public static function getLoggingWithField()

    {

        switch(self::$loginWithField)

        {

            case 'username':

            case 'email':

            case 'usernameOrEmail':

                return self::$loginWithField;

                break;

            default:

                return 'username';

                break;

        }

    }

}



Listing of _CUserIdentity component:


<?php

/**

 * _CUserIdentity represents the data needed to identity a user.

 * It contains the authentication method that checks if the provided

 * data can identity the user.

 */

class _CUserIdentity extends CUserIdentity

{

    const ERROR_UNKNOWN_IDENTITY=10;

    const ERROR_ACCOUNT_IS_INACTIVE=11;

    const ERROR_IS_NOT_ADMINISTRATOR=12;


    private $_id;


    /**

     * @return integer the ID of the user record

     */

    public function getId()

    {

        return $this->_id;

    }


    /**

     * Authenticates a user.

     * @return boolean whether authentication succeeds.

     */

    public function authenticate()

    {

        if(UserLoginForm::getLoggingWithField()=='username')

            $user=User::model()->findByAttributes(array('username'=>$this->username));

        else if(UserLoginForm::getLoggingWithField()=='email')

            $user=User::model()->findByAttributes(array('email'=>$this->username));

        else if(UserLoginForm::getLoggingWithField()=='usernameOrEmail')

            $user=User::model()->find("`username`=? OR `email`=?",array($this->username,$this->username));

        if($user===null)

            $this->errorCode=self::ERROR_USERNAME_INVALID;

        else if(md5($this->password)!==$user->password)

            $this->errorCode=self::ERROR_PASSWORD_INVALID;

        else if($user->isActive===User::IS_NOT_ACTIVE)

            $this->errorCode=self::ERROR_ACCOUNT_IS_INACTIVE;

        else if(MArea::isBackend() && !User::isAdministrator($user->accessType))

        {

            $this->errorCode=self::ERROR_IS_NOT_ADMINISTRATOR;

            Yii::app()->controller->var->userAccessType=$user->getAttributeView('accessType');

        }

        else

        {

            $this->_id=$user->id;

            $this->errorCode=self::ERROR_NONE;

            // do not store password or other sensitive data in the persistent storage

            // when (config/main.php) allowAutoLogin is true, because

            // all these data will be stored in cookie = it is readable

            $this->setState('email', $user->email);

            $this->setState('interface', $user->interface);

            $this->setState('language', $user->language);

            $this->setState('screenName', $user->screenName);

        }

        return !$this->errorCode;

    }


    /**

     * Authenticates a user by cookie.

     * Is called by {@link _CWebUser::restoreFromCookie()}.

     * @return boolean whether authentication succeeds.

     */

    public function authenticateByCookie()

    {

        $user=User::model()->findByPk($this->username);

        if($user===null)

            $this->errorCode=self::ERROR_UNKNOWN_IDENTITY;

        else if($user->isActive==='0')

            $this->errorCode=self::ERROR_ACCOUNT_IS_INACTIVE;

        else

        {

            $this->_id=$user->id;

            $this->errorCode=self::ERROR_NONE;

            // do not store password or other sensitive data in the persistent storage

            // when (config/main.php) allowAutoLogin is true, because

            // all these data will be stored in cookie = it is readable

            $this->setState('email', $user->email);

            $this->setState('interface', $user->interface);

            $this->setState('language', $user->language);

            $this->setState('screenName', $user->screenName);

        }

        return !$this->errorCode;

    }

}