Force login

When someone comes to our site we want to force them to login or sign up. This is because the app is quite useless when you aren't logged in.

To do this, it would be nice if before each route is run we can check if the user is logged in or not. Usually when you want to do something before or after another thing, you want to add a "hook". Most code libraries will allow you to "hook" into their behaviour, and Iron Router doesn't disappoint; from the docs.

"A hook is just a function. Hooks provide a way to plug into the process of running a route, typically to customize rendering behavior or perform some business logic."

Iron Router provides the following hook methods:

  • onRun
  • onReRun
  • onBeforeAction
  • onAfterAction
  • onStop

The one we want is onBeforeAction, as we want to make sure the user is logged in before the route is run.

"onBeforeAction: Called before the route or "action" function is run. These hooks behave specially. If you want to continue calling the next function you must call this.next(). If you don't, downstream onBeforeAction hooks and your action function will not be called."

With this in mind, let's add a hook to our app (as well as a template to render when we aren't logged in):

both/router.js

Router.configure({
  layoutTemplate: 'layout',
  loadingTemplate: 'loading'
});

Router.route('/', {
  name: 'games',
  waitOn: function(){
    return [Meteor.subscribe("games"), Meteor.subscribe("teams")];
  }
});

Router.route('/teams', {
  waitOn: function(){
    return Meteor.subscribe("teams");
  }
});

var requireLogin = function(){
  if(!Meteor.user()){
    this.render("accessDenied");
  } else {
    this.next();
  }
}

Router.onBeforeAction(requireLogin);

client/views/accessDenied.html

<template name="accessDenied">
  <p>Please login or sign up.</p>
</template>

Now if we log out, we are presented with our 'accessDenied' template.

While this will work in most cases, there is one small usecase that hasn't been addressed - what if the user is signing in when our onBeforeAction is called? This would mean the conditional if(!Meteor.user()) would equal true and accessDenied would be shown. We can account for this rare case like so:

both/router.js

...
var requireLogin = function(){
  if(!Meteor.user()){
    if(Meteor.loggingIn()){
      this.render("loading");
    } else {
      this.render("accessDenied");
    }
  } else {
    this.next();
  }
}

Router.onBeforeAction(requireLogin);

It's a rare case and hard to test, but it's handy to know about Meteor.loggingIn().

Finally, if we want we can be more specific over which pages require login:

Router.onBeforeAction(myAdminHookFunction, {
  only: ['games']
  // or except: ['teams', 'someOtherRoute']
});