With Blaze and FlowRouter, how to handle routing for unregistered user?

in our app, we are allowing users to register using social media or useraccounts. this means they can come to the app, hit “Login With Facebook” (from accounts-facebook), and immediately have a user account and logged in.

but in order to access the real content in the app, we need to collect some registration data, e.g. date of birth. We present them immediately with a form to provide this info. once they have done so, we give them a role (alanning:roles), so we can enable/disable features and provide security.

but until then, we’ve taken the approach of checking for that role on Every Single Page, and showing them the “registration” page w/ a form to provide that info.

from what i can tell, this is the Meteor way to do it, or at least the Blaze and FlowRouter way. what i’d love to do, instead, is put in a hook in the routing, detect the lack of role, and route them to a registration page. this seems to be Not The FlowRouter way.

am i correct in my assessment that i’m doing it the intended way? or is there an acceptable way to do the interrupt? do the answers change if we eventually migrate away from Blaze, and to either Angular or React?

Definitely a tough one.

Arunoda suggests that we handle authentication at the template level, but then (as you pointed out), you can end up with auth checks at each template. You could “wrap” your app in a template, which is probably what I would do if you have the flexibility to do so right now.

You can use router hooks, but I can almost guarantee you will run into race conditions where subscription data isn’t ready for the check you need to perform in the hook. If you think this is the best choice for you, pm me and I can walk you through a semi-hacky approach to get a sub-based hook working.

what i would love is to be able to make something like AccountsTemplates.ensureSignedIn, e.g. Registration.ensureRegistered. i looked at the ensureSignedIn code, and it makes use of the userId, which is available at that time.

but you’re right, @vigorwebsolutions, trying to get at anything else (like the roles of the user object) puts me in a bad async situation. i couldn’t figure out a good way to make it work.

could you tell me more about wrapping the app in a template? kind of doing that with the layout, in a way. would doing so involve the layout perhaps?

So you could do something like:

<template name="authCheck">
  {{#if registeredUser}}
    {{> Template.dynamic template=content}}
  {{/if}}
</template>

Template.authCheck.helpers({
  registeredUser () {
    if ( ) { // your auth check
      return true;
    } else {
      FlowRouter.go('loginPath');
    }
});

This solution works at a basic level, but isn’t very dynamic, and feels wrong. Alternatively, you can ignore Arunoda’s suggestion, and leverage the router. Honestly, while I understand scoping auth to the template, being able to create route groups and assign before actions at the group level is just too powerful to ignore (in my opinion).

At the top of my routes.js:

FlowRouter.wait();
let mySub = Meteor.subscribe('items');
Tracker.autorun(function() {
  if (mySub.ready()) {
    FlowRouter.initialize();
  }
});

This basically forces the router to wait on the subscription, and is definitely a hack. I’m on 1.3.4, for reference. Also, I do see some console errors once in a while stemming from the init, but functionally, everything is working.

interesting. for the template one, i assume it’d be better to not evaluate the helper until the subs are ready. since i’m using roles, it could look like this:

<template name="authCheck">
	{{#if Template.subscriptionsReady}}
		{{#unless isInRole 'registered-user' }}
			{{> Registration}}
		{{else}}
			{{> Template.dynamic template=content}}
		{{/unless}}
	{{/if}}
</template>

yeah, the FlowRouter routes.js one looks on the fringe, shall we say, but it’s interesting.

thanks, you’ve given me some stuff to think about.

i looked at this again today, and got it working by interrupting the route, like this:

at the top of lib/routes.js:

if (Meteor.isClient) {
	FlowRouter.wait();
	Tracker.autorun(function() {
		if (Roles.subscription.ready() && !FlowRouter._initialized) {
			FlowRouter.initialize()
		}
	});
}

now i’m guaranteed that the user role (from https://github.com/alanning/meteor-roles) is loaded, and i can make some routing decisions based on that.

thanks!

1 Like

Slightly tighter solution available if using meteor-promise package which exposes subscription.readyPromise():

if (Meteor.isClient) {
  FlowRouter.wait();

  Roles.subscription.readyPromise()
  .then(() => !FlowRouter._initialized && FlowRouter.initialize())
  .catch(console.warn)
}

So no need to set up a Tracker (and then leave it running). This will work fine even though readyPromise() is not reactive and only runs once on initial ready since it runs again on refresh, and until refresh, you’re good.

If I made a mistake somewhere let me know! Been struggling with this for a long time, your solution is nice, clean, and very helpful.