How to set maximum # of failed login attempts on Meteor?

Please help; so I have this working code that shows the user an error (via a dialog box) if they do not enter the credentials I set. But if they enter the correct credentials, it takes the user to the homepage. Now I need to add a limit on the # of failed login attempts.

I also imported DDPRateLimiter, but not sure if i’m doing this right.

What would be the best way to set a limit or a maximum number of failed login attempts? And, can I enter this code within this const Login function I have here? I don’t need anything too fancy

export const Login = () => {
  const [counter, setCounter] = useState(0);

  const increment = () => {
    setCounter(counter + 1);
  };

  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const submit = e => {
    e.preventDefault();

  Meteor.loginWithPassword(username, password);

  Accounts.onLoginFailure(function(attempt) {
    if (attempt.error){
       alert('Login forbidden')
      }    
  DDPRateLimiter.addRule(attempt, 2, 1000);    
    });
  }; 

Hi, it is all described here and has to be done server side: Methods | Meteor API Docs

In your code, you save this in the state so if the user refreshes the page, she can retry forever. I would save this on the user’s profile or on a separate collection and give a grace of 1-5 minutes or so. When a user tries to log in I’d first check if the user is entitled to log in and eventually present the remaining time if that is the case.

Anyway, for most cases the link above could be enough.

Hi, thank you, I check out that documentation and was still confused on how to structure the code unfortunately. The code I have above is located in the Login Page, inside the client server. I’m not sure how the documentation code can fit with what I already have.

Do you know where and what to write inside Meteor.startup() in the server side? This is a fragment of my server side:

const ID= 'email@email.org';
const PASS = 'password';

Meteor.startup(() => {
   if (!Accounts.findUserByEmail(ID)) {
     Accounts.createUser({
       email: ID
       password: PASS,
     });
   }
 });

You can always dig into the “big” Meteor projects to learn best practices especially in the area of App security.
Here’s an example of DDP security implementation:

Sounds good, but how does the client know it works? Like, how can I display a message saying that they’ve reached the max # of attempts? Unfortunately, i don’t see a way to do that in the meteor docs or github

One simple way would be to throw an error in the method on the server side and catch it on client side in the callback

Have you checked the functions under Account package?

How would I throw an error in the method that detects if the user types an incorrect user/pass more than twice? Which function would I use? It seems that my onLoginFailure is overrided if I use another method, so I’ve tried that, so any guidance would be appreciated

Yes, I did, that’s where I got the idea to use the DDPRateLimiter from. And trying validateLoginAttempt did not work for me. I’m just not sure how to implement it because I’m not sure how to turn my meteor.loginwithpassword into a method, in order to get the DDPRatelimiter to work. I tried to use the code I found on stackoverflow and another meteor-forum post, but it just doesn’t work, sadly

Ah ok, haha this is great, I hope it works, but I also wish Meteor had a simpler solution because DDPRateLimiter didn’t work for me since meteor.longWithPassword can’t be housed inside a method/call

DDPRateLimiter is useful but it was not desiged to handle this feature.

oh really? Just so I understand better, what is it used for? Because I saw something about it used for login, but i wasn’t sure how

for example when you don’t want user to run a password scan (brute force ) then you can use DDPRateLimiter to limit the number of login method calls in a period of time.

1 Like

Oh ok! so how is that different from limiting the user from logging in with incorrect credentials?

That’s right. It doesn’t care if user login with correct or incorect password.

1 Like

Oh that makes a lot of sense, thank you!

Sorry, my bad :slight_smile: I think this one will work: onLoginFailure Accounts (multi-server) | Meteor API Docs
You may not need the meteor-method-hooks package.

import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';

Meteor.startup(function () {
  Accounts.validateLoginAttempt(function (attemptInfo) {
    console.log('validate login attemp connection', attemptInfo.connection);
    // TODO: check if this client ip address has exceeded failure attempt count then return.
    // TODO: then return false to prevent user to login
  });
  Accounts.onLoginFailure(function (loginDetails) {
    console.log('on login failure', loginDetails.connection);
    // TODO: increase the login failure count for this client ip address.
  });

It will work, I tested.

1 Like

Oh okay, how do I display this to the client? Because if you scroll all the way up, I have the onLoginFailure in the client. I’m not sure how to test this sorry :frowning: Like, what did you write next to the //todo when you tested it?

The callback of both Accounts.onLoginFailure() and Accounts.validateLoginAttempt() gets passed an “attempt info object”, which also contains a reference to the user object.

In Accounts.onLoginFailure you could update a property e.g. failedLoginAttempts (of type array) in the user document. You would add a timestamp (such as new Date()) to that array for each failed login attempt.

Unset this property once there is a successful login, eventually.

In Accounts.validateLoginAttempt() evaluate the failedLoginAttempts array of the user document. If the rate of failed login attempts was too high, throw an error that you can recognize as such on the client side, and display an error message to the user accordingly.

1 Like