12
votes

using Yii 2 basic Not the advanced version.

I have a crud admin authentication system. Which stores just an id, username and password in the database. When the user goes to log in if the username and password are correct they are logged in.

However i now want to make these passwords secure so i want to salt and hash them. This is the part i am finding hard to do Or more so where to put things.

Part 1: I have an AdminController which goes along with my User Model Create.php page. Part 2: I have a siteController which goes along with the LoginForm model and the login.php page to log in.

I will go over part one first as it will obviously have to actually generate a hashed password here.

AdminController:

public function actionCreate()
{
    $model = new User();

    if ($model->load(Yii::$app->request->post()) && $model->save()) {
        return $this->redirect(['view', 'id' => $model->id]);
    } else {
        return $this->render('create', [
            'model' => $model,
        ]);
    }
}

User.php

    <?php

    namespace app\models;
    use yii\base\NotSupportedException;
    use yii\db\ActiveRecord;
    use yii\web\IdentityInterface;
    use yii\data\ActiveDataProvider;
    /**
     * User model
     *
     * @property integer $id
     * @property string $username
     * @property string $password
     */
class User extends ActiveRecord implements IdentityInterface
{


/**
 * @inheritdoc
 */
public static function tableName()
{
    return 'Users';
}


public function rules(){
        return [
            [['username','password'], 'required']
        ];
}



public static function findAdmins(){
    $query = self::find();

    $dataProvider = new ActiveDataProvider([
        'query' => $query,
    ]);

    return $dataProvider;

}


/**
 * @inheritdoc
 */
public static function findIdentity($id)
{
    return static::findOne(['id' => $id]);
}



/**
 * @inheritdoc
 */
public static function findIdentityByAccessToken($token, $type = null)
{
    throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
}

/**
 * Finds user by username
 *
 * @param  string      $username
 * @return static|null
 */
public static function findByUsername($username)
{
    return static::findOne(['username' => $username]);
}

/**
 * @inheritdoc
 */
public function getId()
{
    return $this->id;
}

/**
 * @inheritdoc
 */
public function getAuthKey()
{
    return static::findOne('AuthKey');
}

/**
 * @inheritdoc
 */
public function validateAuthKey($authKey)
{
    return static::findOne(['AuthKey' => $authKey]);
}

/**
 * Validates password
 *
 * @param  string  $password password to validate
 * @return boolean if password provided is valid for current user
 */
public function validatePassword($password)
{
    return $this->password === $password;
}
}

Question??: So as you can see in this model i only have id, username and password coming from the database, so i take i will need to create one for field in db called "hashed_password"?

create.php:

<?php $form = ActiveForm::begin(); ?>

<?= $form->field($model, 'username')->textInput(['maxlength' => 50]) ?>

<?= $form->field($model, 'password')->passwordInput(['maxlength' => 50]) ?>

<div class="form-group">
    <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>

<?php ActiveForm::end(); ?>

Right so that was part 1, the actual bit where the hashed password needs to be generated and saved into the database, how can i achieve this?

Okay moving on the Part2:

SiteController:

public function actionLogin()
{
    if (!\Yii::$app->user->isGuest) {
        return $this->goHome();
    }

    $model = new LoginForm();

    if ($model->load(Yii::$app->request->post()) && $model->login()) {
        return $this->goBack();
    } else {
        return $this->render('login', [
            'model' => $model,
        ]);
    }
}

LoginForm.php (model):

class LoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;

private $_user = false;


/**
 * @return array the validation rules.
 */
public function rules()
{
    return [
        // username and password are both required
        [['username', 'password'], 'required'],
        // rememberMe must be a boolean value
        ['rememberMe', 'boolean'],
        // password is validated by validatePassword()
        ['password', 'validatePassword'],
    ];
}

/**
 * Validates the password.
 * This method serves as the inline validation for password.
 *
 * @param string $attribute the attribute currently being validated
 * @param array $params the additional name-value pairs given in the rule
 */
public function validatePassword($attribute, $params)
{
    if (!$this->hasErrors()) {
        $user = $this->getUser();

        if (!$user || !$user->validatePassword($this->password)) {
            $this->addError($attribute, 'Incorrect username or password.');
        }
    }
}

/**
 * Logs in a user using the provided username and password.
 * @return boolean whether the user is logged in successfully
 */
public function login()
{
    if ($this->validate()) {
        return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
    } else {
        return false;
    }
}

/**
 * Finds user by [[username]]
 *
 * @return User|null
 */
public function getUser()
{
    if ($this->_user === false) {
        $this->_user = User::findByUsername($this->username);
    }

    return $this->_user;
}
}

Login.php:

<?php $form = ActiveForm::begin(); ?>

<?= $form->field($model, 'username'); ?> 
<?= $form->field($model, 'password')->passwordInput(); ?> 


<div class="form-group">
    <div class="col-lg-offset-1 col-lg-11">
        <?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
    </div>
</div>

So thats it, how do i integrate a hashed_password for each user when they create and then validate this upon login?

I have been reading this on the documentation but just cant get this to work http://www.yiiframework.com/doc-2.0/guide-security-passwords.html

3

3 Answers

22
votes

When you creating user, you should generate and save password hash. To generate it

\Yii::$app->security->generatePasswordHash($password);

To check it on login, change your User model which implements UserIdentity

    /**
     * Validates password
     *
     * @param  string $password password to validate
     * @return boolean if password provided is valid for current user
     */
    public function validatePassword($password)
    {
        return Yii::$app->getSecurity()->validatePassword($password, $this->password_hash);
    }

Instead of password_hash use your field from db.

4
votes

just reference Yii2 advance template user model implement.

/**
 * Generates password hash from password and sets it to the model
 *
 * @param string $password
 */
public function setPassword($password)
{
    $this->password_hash = Yii::$app->security->generatePasswordHash($password);
}

then overriding beforeSave method in User model to hash password before save to DB

public function beforeSave($insert)
{
    if(parent::beforeSave($insert)){
       $this->password_hash=$this->setPassword($this->password_hash);
       return true;
    }else{
       return false;
    }
}

Today we just apply PHP crypt function instead implementing password+salt hash algorithms youself,and it't more security than md5(password+salt)

-2
votes

You don't need a password_hash field in the database. You can use the "password" field to store the hashed password so that it is more secure and difficult to intruder to crack the password. Please modify the files as below,

User.php

<?php
namespace app\models;
use yii\base\NotSupportedException;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
use yii\data\ActiveDataProvider;

class User extends ActiveRecord implements IdentityInterface {


    //public $salt = "stev37f"; //Enter your salt here

    public static function tableName() {
        return 'Users';
    }

    public function rules() {
        return [
            [['username','password'], 'required']
        ];
    }

    public static function findAdmins() {
        $query = self::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        return $dataProvider;

    }

    public static function findIdentity($id) {
        return static::findOne(['id' => $id]);
    }


    public static function findIdentityByAccessToken($token, $type = null) {
        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
    }

    public static function findByUsername($username) {
        return static::findOne(['username' => $username]);
    }

    public function getId() {
        return $this->id;
    }

    public function getAuthKey() {
        return static::findOne('AuthKey');
    }

    public function validateAuthKey($authKey) {
        return static::findOne(['AuthKey' => $authKey]);
    }

    public function validatePassword($password) {
        return $this->password === static::hashPassword($password); //Check the hashed password with the password entered by user
    }

    public static function hashPassword($password) {// Function to create password hash
        $salt = "stev37f";
        return md5($password.$salt);
    }
}

Admin Controller

public function actionCreate() {
    $model = new User();

    if ($model->load(Yii::$app->request->post()) && $model->validate()) {
        $model->password = User::hashPassword($model->password); // Hash the password before you save it.
        if($model->save())
            return $this->redirect(['view', 'id' => $model->id]);
    }
    return $this->render('create', [
       'model' => $model,
    ]);
}

To reset the password, you need to email a password reset link to the user.