[How to] How to prevent multiple logins with Meteor (allowing multiple tabs)

Hello everybody.

I created a small how-to, on using browser fingerprinting to only terminate sessions if it’s a ‘new’ browser, maintaining the capability of the user creating multiple tabs.

For this we use Meteor.logoutOtherClients, a Meteor method, Accounts.onLogin hook and fingerprintjs2.

Any feedback is welcomed.

6 Likes

Although an old topic, I recently had the same requirement for my app. I found a way to do this server-side that doesn’t involve any external dependencies:

// Prevents more than one client from being logged in on same account at any time while allowing original user to remain logged in even after refresh and also open multiple tabs
Accounts.validateLoginAttempt((attempt: any)=> {
	// If authentication failed, skip rest
	if (!attempt.allowed) {
	  	return false;
	}
	// If authentication was successful then:
	else {
		// If user is using resume token, we should allow it if the resume token exists and is the only token stored for user
		if (attempt.type === "resume") {
			const resumeToken = attempt?.methodArguments[0]?.resume;
			if(resumeToken) {
				const userId = attempt?.user?._id;
				if (userId) {
					// We need to compare the hash of the resume token since that is what is stored in DB
					//@ts-ignore
					const hash = Accounts._hashLoginToken(resumeToken);
					const user = Meteor.users.findOne({_id: userId});

					// Check that user exists
					if(user) {
						// Only allow token reuse if user has the resume token in their stored resume tokens, and also that it is the only token
						const tokenReuseAllowed = user?.services?.resume?.loginTokens?.find((token: any) => token.hashedToken === hash) 
							&& user?.services?.resume?.loginTokens?.length === 1;
						// If token reuse is not allowed, we should remove all existing tokens to log out other clients
						if (!tokenReuseAllowed) {
							Meteor.users.update({_id: userId}, {$set: {"services.resume.loginTokens": []}});
						}
					}
				}
			}
		}
		// If user is logging in with password, then we should just remove all existing resume tokens
		else if (attempt.type === "password") {
			const userId = attempt?.user?._id;
			if (userId) {
				Meteor.users.update({_id: userId}, {$set: {"services.resume.loginTokens": []}});
			}
		}
		return true;
	}
});
2 Likes

Nice.

I’ve updated the Medium blog post too: https://medium.com/@raphael.arias/how-to-prevent-multiple-logins-with-meteor-allowing-multiple-tabs-365e89c1a710