Question

The agiletoolkit Auth/basic class allow to try to login without any limitation. And i'm searching a way to limit the number of failed login attempt, i've tried to override some methods of this class but it use ajax reloading so php code is not executed correctly.

Any suggestion is welcome.

Many Thanks,

Was it helpful?

Solution

Inspired by StackOverflow I have implemented the following protection:

for each user we store soft / hard lock. Hard lock would refuse to verify password even if it's correct. soft lock is a period of time after which we will reset "unsuccessful attempt" counter. every time you type incorrect password both soft and hard lock increase making you wait longer (but not forever) gradual increase puts a reasonable limit so if your attacker tries to hack your account for a hour - he will only get several attempts in, yet your account will be OK to log-in after few hours. I have implemented this in one of my projects, although it's not a controller, but a built-in code. Please look through comments and I hope this will help you and others:

In User model add:

$this->addField('pwd_locked_until');
$this->addField('pwd_failure_count');
$this->addField('pwd_soft_unlock');

You'll also need two methods:

/* Must be called when user unsuccessfully tried to log-in */
function passwordIncorrect(){
    $su=strtotime($this['pwd_soft_unlock']);
    if($su && $su>time()){
        // aw, they repeatedly typed password in, lets teach them power of two!
        $this['pwd_failure_count']=$this['pwd_failure_count']+1;
        if($this['pwd_failure_count']>3){
            $this['pwd_locked_until']=date('Y-m-d H:i:s',time()
                +pow(2,min($this['pwd_failure_count'],20)));

            $this['pwd_soft_unlock']=date('Y-m-d H:i:s',time()
                +max(2*pow(2,min($this['pwd_failure_count'],20)),60*5));
        }
    }else{
        $this['pwd_failure_count']=1;
        $this['pwd_soft_unlock']=date('Y-m-d H:i:s',time() +60*5);
    }
    $this->save();
}

and

/* Must be called when user logs in successfully */
function loginSuccessful(){
    $this['last_seen']=date('Y-m-d H:i:s');
    $this['pwd_soft_unlock']=null;
    $this->save();
}

Finally - you can use this as a login form:

class Form_Login extends Form {
  function init(){
    parent::init();

    $form=$this;
    $form->setModel('User',array('email','password'));
    $form->addSubmit('Login');

    if($form->isSubmitted()){

        $auth=$this->api->auth;

        $l=$form->get('email');
        $p=$form->get('password');

        // check to see if user with such email exist
        $u=$this->add('Model_User');
        $u->tryLoadBy('email',$form->get('email'));

        // user may have also typed his username
        if(!$u->loaded()){
            $u->tryLoadBy('user_name',$form->get('email'));
        }

        // incorrect email - but say that password is wrong
        if(!$u->loaded())$form->getElement('password')
            ->displayFieldError('Incorrect Login');

        // if login is locked, don't verify password at all
        $su=strtotime($u['pwd_locked_until']);
        if($su>time()){
            $form->getElement('password')
                ->displayFieldError('Account is locked for '.
                $this->add('Controller_Fancy')
            ->fancy_datetime($u['pwd_locked_until']));
        }

        // check account
        if($auth->verifyCredentials($u['email'],$p)){
            // resets incorrect login statistics
            $u->loginSuccessful();
            $auth->login($l);

            // redirect user
            $form->js()->univ()->location($this->api->url('/'))->execute();
        }else{
            // incorrect password, register failed attempt
            $u->passwordIncorrect();
            $form->getElement('password')->displayFieldError('Incorrect Login');
        }
    }
  }
}

This should be converted into add-on by someone.

OTHER TIPS

I think you may store numbers of usage of Model_User (usually used in Auth) in session or cookies using afterLoad hook and then check it where you need to.

Oops. FAILED login. So you model is NOT loaded. You need just to count clicks on a button of a login form and store it somewhere (cookies, database). So create your own login form and add some condition when a form is submitted like:

    $m = $this->add('Model_User');

    $f = $this->add("Form");
    $f->setModel($m, array('email', 'password'));
    $f->addSubmit("Log In");

    if ($f->isSubmitted()){
        //Limmiting
        if($_COOKIE[$this->api->name."_count_failed_login"] >= 5/*Here's you limit number*/){
            /*redirect or something else*/
        }

        if (/*here should be you condition*/){
            $_COOKIE[$this->api->name."_count_failed_login"] = 0;
            $this->api->auth->login($f->get("email"));
            $f->js()->univ()->redirect("index")->execute();
        }else{
            /* when login is failed*/
            if($_COOKIE[$this->api->name."_count_failed_login"]){
                $_COOKIE[$this->api->name."_count_failed_login"]++;
            }else{
                $_COOKIE[$this->api->name."_count_failed_login"] = 1;
            }
            $this->js()->univ()->alert('Wrong username or password')->execute();
        }
    }

I didn't check it. Maybe some adjastements are needed. Just an idea.

Hope that helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top