All Articles

How to test if user access has been revoked in a Meteor application

For a client application I recently had to build a user authentication system that allowed administrators to revoke access to certain users or groups of users.

Initially I planned on handling this process using a Meteor.method() that was called when the user submitted the login form. If the method threw an error the login would fail, otherwise the login would be allowed in the callback. It looked something like this:

Meteor.call("Users.Methods.Users.CheckRevoked", email, (error) => {
  if (error) {
    Bert.alert(error.reason, "danger", "growl-top-right");
  } else {
    Meteor.loginWithPassword(email, password, (error) => {
      if (error) {
        Bert.alert(error.reason, "danger", "growl-top-right");
      } else {
        Bert.alert("Logged in!", "success", "growl-top-right");
        browserHistory.push("/dashboard/calendar");
      }
    });
  }
});

After a bit of thinking and reading the API docs I found a much better pattern for checking a user’s authentication details before allowing/forbidding a login.

Accounts.validateLoginAttempt

The Accounts.validateLoginAttempt() (docs) function is a server only function that allows your application to intercept a user’s login and test it before returning whether or not it should proceed.

From the docs:

Called whenever a login is attempted (either successful or unsuccessful). A login can be aborted by returning a falsy value or throwing an exception.

Using this pattern I was able to easily check whether a user is allowed to login to the application or whether they have had their access revoked.

In my particular application, each user belonged to a team, and each team document in the Teams collection had a boolean value called revoked.

import { Accounts } from "meteor/accounts-base";
import { Meteor } from "meteor/meteor";
import { Teams } from "/imports/api/teams";

Accounts.validateLoginAttempt((options) => {
  // If the login has failed or no user is found, just return false.
  if (!options.allowed || !options.user) {
    return false;
  }
  // Grab the user's teamId from their user document
  const teamId = Meteor.users.findOne(options.user._id).teamId;

  // If the team has had their access revoked, throw an error
  if (Teams.findOne(teamId).revoked) {
    throw new Meteor.Error(
      "problem-logging-in",
      "Your access has been revoked."
    );
  }

  // User exists & team has access, return true to allow login
  return true;
});

The options argument being passed to the validateLoginAttempt() function is a an object containing information about the login attempt that can be used to test whether it should proceed or not. You can see what it contains by reading the Accounts.validateLoginAttempt() section of the Meteor docs.