In this wiki I will show how could wo pass the third parameter to UserIdentity on login authenticate. So we can do separate login authentcation from two or more different models from single login form.
In LoginForm Model -
Added a new variable like - usertype. also we will pass it's to UserIdentity same as username & password.
eg.
$this->_identity = new UserIdentity($this->username, $this->password, $this->usertype);
class LoginForm extends CFormModel
{
public $username;
public $password;
public $usertype;
public $rememberMe;
private $_identity;
/**
* 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 and password are required
array('username, password, usertype', 'required'),
// rememberMe needs to be a boolean
array('rememberMe', 'boolean'),
// password needs to be authenticated
array('password', 'authenticate'),
);
}
/**
* Declares attribute labels.
*/
public function attributeLabels()
{
return array(
'rememberMe'=>'Remember me next time',
'usertype'=>'User Type',
);
}
/**
* Authenticates the password.
* This is the 'authenticate' validator as declared in rules().
*/
public function authenticate($attribute, $params)
{
if(!$this->hasErrors())
{
$this->_identity = new UserIdentity($this->username, $this->password, $this->usertype);
if(!$this->_identity->authenticate())
$this->addError('password','Incorrect username or password.');
}
}
/**
* Logs in the user using the given username and password in the model.
* @return boolean whether login is successful
*/
public function login()
{
if($this->_identity===null)
{
$this->_identity = new UserIdentity($this->username, $this->password, $this->usertype);
$this->_identity->authenticate();
}
if($this->_identity->errorCode===UserIdentity::ERROR_NONE)
{
$duration=$this->rememberMe ? 3600*24*30 : 0; // 30 days
Yii::app()->user->login($this->_identity,$duration);
return true;
}
else
return false;
}
}
In Login View -
Here i'm using two different user type authentication - User & Agency
<?php $form = $this->beginWidget('CActiveForm', array(
'id'=>'login-form',
'enableClientValidation'=>true,
'clientOptions'=>array(
'validateOnSubmit'=>true,
),
)); ?>
<?php echo $form->labelEx($model,'username'); ?>
<?php echo $form->textField($model,'username', array('class'=>'form-control', 'placeholder'=>'Username')); ?>
<?php echo $form->error($model,'username'); ?>
<?php echo $form->labelEx($model,'password'); ?>
<?php echo $form->passwordField($model,'password', array('class'=>'form-control', 'placeholder'=>'Password')); ?>
<?php echo $form->error($model,'password'); ?>
<?php echo $form->labelEx($model,'usertype'); ?>
<?php echo $form->dropDownList($model, 'usertype', array('U' => 'User', 'A' => 'Agency'),
array('empty' => '-- Select User Type --', 'class'=>'form-control')); ?>
<?php echo $form->error($model,'usertype'); ?>
<?php echo CHtml::submitButton('Login', array('class'=>'btn-login')); ?>
<?php $this->endWidget(); ?>
In Controller (actionLogin method) -
public function actionLogin(){
$model = new LoginForm();
if (isset($_POST['ajax']) && $_POST['ajax'] === 'login-form'){
echo CActiveForm::validate($model);
Yii::app()->end();
}
if (isset($_POST['LoginForm'])){
$model->attributes = $_POST['LoginForm'];
if ($model->validate(array('username', 'password', 'usertype')) && $model->login())
$this->redirect(array('site/index'));
}
$this->render('login', array('model' => $model));
}
In UserIdentity
Need to update UserIdentity.php (In Component directory). Here we will getting third parameter 'usertype' as per we define & pass in LoginForm model.
We will update the CUserIdentity construct method to getting third parameter 'usertype' values. http://www.yiiframework.com/doc/api/1.1/CUserIdentity#__construct-detail
class UserIdentity extends CUserIdentity {
/**
* @var integer id of logged user
*/
private $_id;
private $_first_name;
public $usertype;
//Must need to add
public function __construct($username, $password, $usertype)
{
$this->username = $username;
$this->password = $password;
$this->usertype = $usertype;
}
/**
* Authenticates username and password
* @return boolean CUserIdentity::ERROR_NONE if successful authentication
*/
public function authenticate() {
$attribute = strpos($this->username, '@') ? 'email' : 'username';
if($this->usertype == 'A'){ //User type 'A' indicate to Agents
$login = Agent::model()->find(array('condition' => $attribute . '=:loginname', 'params' =>
array(':loginname' => $this->username)));
}
else if($this->usertype == 'U'){ //User type 'U' indicate to Normal Users
$login = User::model()->find(array('condition' => $attribute . '=:loginname', 'params' =>
array(':loginname' => $this->username)));
}
//Login Authentication & Validation
if ($login === null) {
$this->errorCode = self::ERROR_USERNAME_INVALID;
}
else if ($login->password!= md5($this->password)) {
$this->errorCode = self::ERROR_PASSWORD_INVALID;
}
else {
$this->_id = $login->id;
$this->username = $login->email;
$this->_first_name = $login->first_name;
$this->setState('firstName', $login->first_name);
$this->setState('userType', $this->usertype); //Store user type for sepration
$this->errorCode = self::ERROR_NONE;
}
return !$this->errorCode;
}
/**
*
* @return integer id of the logged user, null if not set
*/
public function getId() {
return $this->_id;
}
public function getFirstName() {
return $this->_first_name;
}
}
Multi user login facility from single login from. Try this, working great in yii 1.1
Duplicate ids
The code above pulls user data (including ids) from either User or Agency tables. If you are using integers, especially auto increments for ids, they will be duplicated (e.g. different users with id=2 will exist in both User and Agency) and this needs to be handled throughout the whole application. For example you can't just take Yii::app()->user->id, you always need to compare it with user type and act accordingly.
Having two different tables for users is also troublesome if you have references to user ids in other tables - you can't use constraints and do joins on tables if the foreign key may reference more than one table.
I would use single BaseUser table containing common fields (id, first name and last name etc.) and add RegularUser and AgencyUser tables with 1:1 relations for regular users and agencies respectively with any extra information that differs. Then define User and Agency views as joins of BaseUser and RegularUser/AgencyUser and base my models on them. Or if you don't like views, use normal Yii relations.
RE #18634
I got your point but here you also store the userType for the sepration between USER & AGENCY.
$this->setState('userType', $this->usertype); //Store user type for sepration
Sure
Absolutely, I didn't mean to say it's wrong. I just wanted to point out some limitations.
Some remedy to duplicate ids could be not using integer autoincrement for primary key, but a text identifier that is meant to be unique (e.g. for normal users starting with 'U' and for agencies with 'A'). But the foreign key constraint problem still exists.
If you don't need foreign keys or don't store user ids in other tables, your solution is pretty much OK. All depends on specific application.
Why?
I think that use two models for autenticate is a big mistake. You just need a table for the users and a table for the roles it is much easier and correct.
If u look in the extensions repository u will find many different implementations.
RE #18645
@davidoff Yupp your correct but i got many place programmers need authentication from two different models. But we can authentication using single model for two or more different type users. we will user usertype to separate them.
Updated UserIdentity -
Login Authentication using single model.
class UserIdentity extends CUserIdentity { /** * @var integer id of logged user */ private $_id; private $_first_name; public $usertype; //Must need to add public function __construct($username, $password, $usertype) { $this->username = $username; $this->password = $password; $this->usertype = $usertype; } /** * Authenticates username and password * @return boolean CUserIdentity::ERROR_NONE if successful authentication */ public function authenticate() { $user = Login::model()->findbyAttributes(array('username' => $this->username, 'usertype' => $this->usertype)); //Login Authentication & Validation if ($user === null) { $this->errorCode = self::ERROR_USERNAME_INVALID; } else if ($user->password!= md5($this->password)) { $this->errorCode = self::ERROR_PASSWORD_INVALID; } else { $this->_id = $user->id; $this->username = $user->email; $this->_first_name = $user->first_name; $this->setState('firstName', $user->first_name); $this->setState('userType', $this->usertype); //Store user type for sepration $this->errorCode = self::ERROR_NONE; } return !$this->errorCode; } /** * * @return integer id of the logged user, null if not set */ public function getId() { return $this->_id; } public function getFirstName() { return $this->_first_name; } }
RE #18664
I hot your point
But use a single model for auth with a role model is still more flexible, in this way u could imagine also that an user can have different roles and apply different rules depending on roles.
Lets imagine that an user can have only a role. The info u have in usertype is exactly the role of the user.
If u want keep ur structure u should define the roles table anyway and made the usertype be simply an external key, so when u need a new role (usertype) u just have to had it to the table without change the code.
It is always better to add a row in a table then write new pieces of code.
You could also think to use a view for auth .... made a join between users and roles so u can use a single activerecord.
But honestly i still think that ur solution is a complex way to made not flexible a simply rbac auth.
I see u let choose the user wich ore he can have at login time, but i think it is useless. Think about the unix login... each user can have different groups and some ACL depends on the group.
If the users have as example different tables for profiles (it is a DB project error, but can come from legacy code) it is better to have a lookup table and check after the auth which profile u have to load. Or maybe u could imagine to write an SQL procedure that create a new Table for profiles loading inside the values of the other tables with the fields translation u want use and using NULL for fields that a table dont have.
(Sorry for my poor english)
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.