Meteor accounts-ui ie8 fix

Using Simple Meteor Authentication with IE8 via accounts-ui

So your client ruined your day with a requirement that you support IE 8 on your new and exciting Meteor project, eh? Yikes…

Mixing the newest, freshest framework around with the oldest, most frustrating browser to have ever existed (well beside IE 6 & 7) is generally an exercise in extreme frustration. But after the initial shock, it can actually be quite a fun challenge to make work. In truth, it isn’t really all that difficult of a problem to overcome.

In a recent project, we had a brief moment of panic when we realized that Meteor’s built-in authentication was causing visitors using IE8 to experience a browser timeout. Meteor’s javascript was simply overloading what the IE8 javascript engine could handle.

After investigation, we realized that a Meteor security feature that ensures no clear-text passwords are sent across the internet during authentication (a really innovative and cool bit of functionality!) was causing the issue. It turned out that the encryption algorithm was simply too intensive for IE8 to handle without a popup warning announcing to the user that, “The browser has become unresponsive, would you like to stop the script from running?”. If you continued to wait, the browser would eventually catch up, but that popup was obviously not an acceptable user experience - especially seeing as if you chose to stop the script, the app would entirely lock up (a Meteor app without Javascript is a blank white page, after all).

We concluded that our only option was to bypass this security feature for IE8 users (which obviously is less secure, but as long as the password is transmitted over https it’s not any less secure than 99.9% of all other applications on the web today).

In the end, it was actually a fairly simple update and our implementation provides a nice example of how Meteor can be tweaked to behave differently. In fact, even if you don’t need this solution for your project, the code snippet below provides a model for how you can interact with Meteor’s login system for your own purposes.

Please note that it’s extremely important you weigh the security implications of this before you decide to move forward with this update.

You should probably keep in mind the following points:

  • Is IE8 a requirement? If you can ditch IE8 then you should to keep the security model intact. Depending on your project, this might be super simple, but many companies in finance and healthcare still use IE8 quite heavily.
  • You will need to re-evaluate the code over time to ensure that you haven’t inadvertently exposed any security vulnerabilities (just as Meteor does with the core framework).
  • You must use SSL on your application so clear-text passwords are protected against network snooping. Standard https stuff.


Below you’ll see a simple code snippet with a slight tweak to the server side login function, as well as a tweak to the client-side login view.

The code


//client:

Template.login.events({
  'submit': function(event) {
    
    event.stopPropagation();
      event.preventDefault();
      
    Meteor.clientCustomLogin($('#user').val(), $('#password').val(), function (err) {
      if (err) alertify.alert(err.reason || "Invalid Login");
      else Router.go('/');
    });
    
  },
});

Meteor.clientCustomLogin = function (user, password, callback) { 

//meteor selects login method based on the argument names. If you use 'user' and 'password' it will use default login method
  Accounts.callLoginMethod({
    methodArguments: [{clientUser: user, clientPassword: password}],
    userCallback: callback
  });
};

//server:

//register login handler

Accounts.registerLoginHandler("clientLogin", function (options) {
 
  if (!options.clientUser || ! options.clientPassword)
    return undefined; // don't handle
 
  if (options.clientUser.indexOf('@') != -1) {
    var selector = {"emails.0.address": options.clientUser}
  }
  else {
    var selector = {"userID": options.clientUser};
  }
  var user = Meteor.users.findOne(selector);
 
  if (! user || !user.services || !user.services.password
        || !user.services.password.srp || !user.services.password.srp.verifier) {
    return {
      userId: undefined,
      error: new Meteor.Error(403, "User not found")
    };
  }
 
  var SRP = Package.srp.SRP;
  var verifier = user.services.password.srp;                                              
  var newVerifier = SRP.generateVerifier(options.clientPassword, {identity: verifier.identity, salt: verifier.salt});
 
  if (verifier.verifier !== newVerifier.verifier) {
    return {
      userId: undefined,
      error: new Meteor.Error(403, "Invalid username or password.")
    };
  }
 
  return {
    userId: user._id
  };
});