13

I'm looking for the most elegant way to lock a Django user account after several failed login attempts.

"What have I tried?":

I have looked unsuccessfully for similar questions on SO (If this question is a dup please, post a comment to delete this one).

At the moment I'm looking for other developers' experiences. I would prefer not to talk about what I've tried in order to not condition the answers.

As additional information, the app doesn't have UserProfile enabled (but, of course, I can enable it if it's worth it).

Jacinda
  • 4,424
  • 2
  • 24
  • 37
dani herrera
  • 39,746
  • 4
  • 87
  • 153
  • 2
    Locking out *users* based on failed login attempts is poor design in my opinion. A malicious third party could intentionally hammer away at known (or expected) accounts, locking out the actual user. Blocking (or possibly hell-blocking) based on IP or the like would be preferred. If you do want that higher security, perhaps a mix of the two, but where the IP block occurs after fewer attempts. – Nick T Sep 27 '13 at 17:53
  • Isn't the whole point that you're locking an account against an unauthenticated user? A minor inconvenience is much better than a compromised account, no? – DylanYoung Jun 21 '17 at 16:37
  • @NickT What is "hell-blocking"? – kbuilds Dec 07 '18 at 05:18
  • @kbuilds coined from hell-banning or shadow-banning: https://blog.codinghorror.com/suspension-ban-or-hellban/ Implementing some system that after X failed logins from an IP, it keeps responding in a seemingly-normal way (for a failure) but didn't really try at all. – Nick T Dec 07 '18 at 21:19

5 Answers5

16

Take a look at django-axes or django-brutebuster

Eduard Luca
  • 6,036
  • 14
  • 73
  • 127
DrTyrsa
  • 27,759
  • 7
  • 79
  • 83
  • nice, thanks Dr! But this don't lock account (is not a solution for me). Have you tested any of both? Someone has experience using it in a real environment? – dani herrera Jan 27 '12 at 13:02
  • 2
    @danihp `django-brutebuster` creates a model instance for every failed login attempt and records username there. So I think it's rather elegant to add `post_save` signal to this model, and lock user there (one line of code). I tried django-brutebuster just for logging, but not in production (didn't need to). – DrTyrsa Jan 27 '12 at 13:19
  • let to me some time to make deliberations ;) – dani herrera Jan 27 '12 at 19:22
  • I think that django-brutebuster + post_save signal is the most close answer to my question. Also, django-lockup is a nice tool, and useful for prevent bruteforce, but I need to lock user account permanently, not only temporarily in order to comply with local laws. – dani herrera Jan 29 '12 at 20:27
  • You can lock out the user (instead of IP) with django-axes by setting AXES_ONLY_USER_FAILURES, see https://django-axes.readthedocs.io/en/latest/4_configuration.html (since around 2016) – Risadinha Jul 26 '19 at 08:23
7

We used django-lockout and it worked really well

UPDATE: django-lockout's last release was 2011: https://pypi.org/project/django-lockout/. The Github project does not exist anymore (404).

Risadinha
  • 13,364
  • 2
  • 74
  • 80
Michael Samoylov
  • 2,396
  • 2
  • 22
  • 32
  • Thanks to share knowhow. I have read lockoup doc and I have a question: lock is only at cache level? Is there a way to mark user as locked in database? – dani herrera Jan 27 '12 at 16:14
  • Cache only. It makes sense to reduce load (no extra DB queries) when morons are trying to bruteforce your system ;-) – Michael Samoylov Jan 27 '12 at 18:40
  • @MichaelSamoylov - this link is not valid anymore - Github returns a 404. Does this projekt still exist? – Risadinha Jul 26 '19 at 08:19
  • @Risadinha it's been more than seven years since my comment. https://pypi.org/project/django-lockout/ is dead for sure. Try https://github.com/jazzband/django-axes maybe? – Michael Samoylov Jul 26 '19 at 12:18
4

One simple solution would be to create a variable in the User Profile that is initialy 0 and increased by 1 every time the user unsuccessfully tries to login. If this variable reaches a certain threshold(which is checked every time the user tries to login), the user account can be suspended. Of course when the user does succesfully login, the variable must be set back to 0.

jörg
  • 3,031
  • 3
  • 18
  • 12
  • 3
    If you do it this way it would be possible to lock out someone else by just trying to log in with their username a couple of times. Locking username only in combination with the IP is probably a good idea. – kioopi Sep 04 '12 at 15:30
  • Beside the hint by @kioopi a solution that does not need to hit the database would be probably preferable. – Max Mar 10 '16 at 10:54
2

Create model called "failed_logins" with two fields, a "User" field/foreign key and a "Timestamp" field.

When a user successfully logs in, delete all "failed_logins" entries for that user.

When a user unsuccessfully logs in, create an entry in "failed_logins" for that user with the current timestamp.

On every login attempt for a given user, BEFORE checking to see if password is correct/incorrect:

  • run a query deleting all "failed_logins" entries older than 15 minutes (or w/e time period).

  • run a query checking the count of entries in failed_logins for the user attempting to login. If it's 5, kill the login attempt, notifying the user they have been locked out of their account and to try back in a little while.

Result: Users are locked out after 5 failed login attempts for a short while.

emeth
  • 344
  • 4
  • 8
  • 2
    Your solution has the problem, that anyone can lockout other users, because your lock is account-based. Furthermore there would be unnecessary many database hits. – Max Mar 10 '16 at 10:55
-7

You can try this snippet:

<?php
session_start();

$conn = mysql_connect("localhost","root","root");
$select = mysql_select_db("Gayathri",$conn) or die("connection error");

if(isset($_COOKIE['login'])&&$_COOKIE['login']>=3){
echo " you have to wait for sometimes to enable your login"."<br>";
}

if(isset($_POST["username"],$_POST["userpassword"])){
    $username = $_POST['username'];
    $userpassword = $_POST['userpassword'];

    $query = "SELECT * from userlogin where username='".$username."' and userpassword='".$userpassword."'";
    $result = mysql_query($query);
    $row = mysql_fetch_assoc($result);

if($row){
    echo"login successfull";
}
else{
    echo"Invalid password"."<br>";
if(isset($_COOKIE['login'])){
    $attempts=$_COOKIE['login']+1;
       }else{
    $attempts=1;
}
setcookie('login', $attempts, time()+60*60);
}

}
?>
<form method = "post" action = "">
<table border=1>
<tr>
    <td>Username:</td>

    <td><input type="text" name="username" value=""></td>
</tr>
<tr>
    <td>Userpassword:</td>

    <td><input type="password" name="userpassword" value=""></td>
</tr>
<tr>
    <td colspan="2" align=center><input type="submit" name="submit" value="login"></td>
</tr>
</table>
John Moutafis
  • 18,296
  • 5
  • 55
  • 96
  • 2
    Warning: The `mysql_*` functions are [no longer supported or maintained](https://stackoverflow.com/questions/12859942). They were [deprecated in PHP 5.5.0](https://wiki.php.net/rfc/mysql_deprecation) and [removed in PHP 7.0.0](https://php.net/manual/en/function.mysql-connect.php#function.mysql-connect-refsynopsisdiv). You are strongly encouraged to migrate to either [MySQLi](https://php.net/manual/en/book.mysqli.php) or [PDO_MySQL](https://php.net/manual/en/ref.pdo-mysql.php). – Pang Apr 20 '17 at 05:33
  • 2
    Warning: Your code is vulnerable to [SQL injection attacks](https://en.wikipedia.org/wiki/SQL_injection). Please read [this post](https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php) to learn more on how to prevent it. – Pang Apr 20 '17 at 05:33