How to redirect a user after server-side logout with Accounts.setPassword?


#1

I am new to Meteor and trying to build a community user management app where the admins (rather than the users themselves) will create and manage the user accounts.

The Accounts are created via a server-side call to Accounts.createUser() and admins then set the password via a call to Accounts.setPassword(). So far so good, I’ve got most of this working nicely.

Individual users can also log themselves out without a problem using AccountsTemplates.logout(), and doing so redirects them correctly to FlowRouter.go('/sign-in') using AccountsTemplates.configure({onLogoutHook: myPostLogout}).

But… according to the official Meteor Guide, the Accounts.setPassword() function also allows you to “logout all current connections with this userId” but I’m completely stuck on how to capture and redirect the user. I currently have the following:

/imports/startup/client/index.js

Template.body.onRendered(function() {

	/* REDIRECT USER WHEN PASSWORD IS CHANGED BY ADMIN */
	var lastUser = null;
	Tracker.autorun(() => {
	const userId = Meteor.userId();
		if ( !userId ){
			console.log("No Meteor.userId() found!");
		} else if (lastUser) {
			console.log("Tracker.autorun detected change in Meteor.userId()",userId,lastUser);
			FlowRouter.go('/sign-in');
		}
		lastUser = Meteor.user();
	});
});

Although the Tracker.autorun appears to detect the logout (note - I do get the log: “No Meteor.userId() found!” in the console) the FlowRouter.go() redirect is ignored and the user can continue to browse the app - moving between routes etc.

It’s important to mention that the supposedly reactive value of Meteor.userId() remains available even after the logout. The only indication that the user has actually been logged out by the server, is that a page refresh (F5) will force the user back to the sign-in page, as will duplicating or opening a new tab for the same app.

What have I done wrong here? How I can correctly detect the server-side logout? Or is there another/better way to force this remote user to logout and redirect?


Logout and redirect if server is unreachable
#2

I am seeing something similar. In a React component, I call Meteor.logout. This triggers my Accounts.onLogout callback function which redirects me to the root of the application /.

All my routes have an onEnter prop (I’m using react-router) which allows me to redirect if they don’t have access. For public pages (like the root signup page) I redirect them into the app if they are already authenticated.

When I logout, my Meteor.logout function fires and I do get redirected to the root. The problem is the root of the app, my signup page, can still fetch the id via Meteor.userId(). This means the user is logged in, and I get redirected into the app.

I’m not sure why Meteor.userId() is available after Accounts.onLogout has already fired.

Edit: I noticed the official docs for Accounts.onLogout is under the “Accounts (multi-server)” header. Seems to not be designed for our purposes.

You can use the callback function for Meteor.logout. This function runs and does not have access to Meteor.userId().

You could also wrap you Accounts.onLogout code is a 0ms setTimeout to wait for the stack to clear. This worked for me:

Accounts.onLogout(() => {
  console.log('User id', Meteor.userId()); // will return the user id
  setTimeout(() => {
    console.log('User id 2', Meteor.userId()); // will return undefined
  }, 0);
});


#3

@andrewmead - Thank you for your super-useful input! Your suggestion for the timeout to allow the Meteor.userId() to clear was the missing piece for me, so I now have it working at last with a combination of your setTimeout() technique and @saimeunt’s userDisconnected method as documented in this StackOverflow discussion.

Just for the record - I do think there is something fundamentally missing in Meteor here in terms of a functional onServerLogout hook for detecting a server-side logout (as opposed to the more common onClientLogout scenario) but as I haven’t really seen this requirement referenced elsewhere, this doesn’t appear to be a common requirement unfortunately.

For those interested, my solution wasn’t pretty but looks something like this:

/imports/startup/client/index.js

Template.body.onRendered(function() {

	Tracker.autorun(() => {
		/* Probably overkill to check for all of these but it works... */
		if ( !Meteor.loggingIn() && (!Meteor || !Meteor.user() || Meteor.user() == null || !Meteor.userId()) ) {
			console.log("$ === TRIGGERED DURING LOG OUT PROCESS ===");
		} else if ( Meteor.loggingIn() ) {
			console.log("$ === TRIGGERED DURING LOGGING IN PROCESS ===");
		} else {
			console.log("$ === TRIGGERED DURING STANDARD USAGE ===");
				var sResponse;
				Meteor.call("checkUserIsLoggedIn", Meteor.userId(), function(result1,result2){
					sResponse = result2;
				});
				setTimeout(() => {
					serverResponseFollowUp("timeout", sResponse);
				}, 2000);

				serverResponseFollowUp = function (context, response) {
					var msg;
					if (response == "userFound") {
						msg = "Server confirms user is logged in ("+context+"/"+response+")";
						sAlert.success(msg);
					} else {
						msg = "Server indicates <strong>remote logout</strong> ("+context+"/"+response+")";
						sAlert.error(msg, {html:true});
						/* Force a page reload as Meteor server-side logout does *not* do this prior to killing session  */
						window.location.reload();
					}
				}
		}
	});

	...
	...

/imports/api/users/methods.js

Meteor.methods({
    ...
    ...
    checkUserIsLoggedIn:function(userId){

        try {

        	check(userId, String);

	        var user = Meteor.users.findOne(userId);

			if (typeof user == "object"){
				return "userFound";
			} else if (typeof user == "undefined"){
				return "userNotFound";
			} else {
       				return "noResponse";
			}

       	} catch (err) {
       		return "Error: " + err;
       	}

    },
    ...
    ...