[Solved] Unwanted account creation when signing in with Google

Hi all,

My app implements the Google and Facebook login. The trouble is that there seems to be no differentiation between “logging in” with Google and “registering” with google - they both go through the oauth process and create an account if one does not already exist.

I have users who register with my service using the normal email and password mechanism (say with a hotmail account), then discover the “Login with Google” button and think they can press that button and get logged in under their hotmail account! Result - two logins and confusion reigns.

I really only want to create a new account when the user clicks the “register with google” button and refuse login with “login with google” when the email does not exists.

Any ideas?

Since you actually hand over the login process to Google, you only get feedback afterwards.

Which means that you have to go at it the other way around: Upon creation of a new account, you have to see if there’s another “native” account using the same email address. And then link the two.

Thanks for your reply - the problem is that it’s not the same email (you can’t have two accounts using the same email). I can’t reliably detect that two differently created accounts with different emails are, in fact, the same intended account…

If it’s not the same email then I’m not sure how you intend to block the OAuth request in the first place?

And I’m moderately certain that you can have two accounts with the same email address - because the OAuth accounts store the login data differently from the “native” ones. And as far as I know, the OAuth accounts are not queried when looking up email addresses.

Hi again, You’re right that the oAuth happens entirely independently - and I’m not really expecting to block that part of it. It’s just that, when it returns we have a google authenticated user that, when Meteor looks to see if that user (i.e. their email) is in the user table, it will create a new user if its not. I don’t want it to do that. I want it to reject the attempted login because the user does not exist! I only want it to create a user if the oAuth sequence began with a “Register with google” initiation from the app.

You’re very confusing with your syntax here.

OK, sorry about that - thanks for trying to help anyway

Hi @dthwaite,

I don’t think there’s a flag that lets you choose whether to create an account or not in the accounts-oauth package. So what you’ll have to do is use a local copy of the package, which meteor will use instead of the online one.

Steps:

  1. Download the accounts-oauth package folder from: https://github.com/meteor/meteor/blob/master/packages/accounts-oauth/
  2. Extract into the packages/accounts-oauth folder of your app
  3. Edit oauth_server.js to check if a user exists first

You can see where it currently creates a user here:

The easiest way to do this is to look up if a user exists with the same email:

Accounts.findUserByEmail(result.serviceData.email)

Or, if they have previously signed up with google:

Meteor.users.findOne({'services.google.id': result.serviceData.id});

and return an error if they don’t have an account:

return { 
    type: "oauth",
    error: new Meteor.Error(
        Accounts.LoginCancelledError.numericError,
        "No matching user found"
    )
};

Yah, but from what I gathered, this is the real problem:

I can’t reliably detect that two differently created accounts with different emails are, in fact, the same intended account

Which is a bit of a problem indeed :wink:

Yes, I understand all this - excellent guidance thank you. Only issue is that, at this point in the code I do not know whether the oAuth was initiated by clicking the “Register with google” button (signup) or with the “Login with Google” button (signin). For the former I do want to create an account, for the latter I do not!

@coagmano’s advice to copy the package and edit it is correct.

Otherwise, you have a few options:

  1. You can have users always type in their email addresses, and check to see what their login/signup options are. This would start the signup/login flow with one field, “Your E-Mail Address.”
  2. You should always show the social login buttons, and then add a URL that says, “Create an account instead.” So that it takes two steps to create an e-mail and password based account.

Note, in Meteor, you can always promote social accounts to have passwords by simply adding the password service entry in the user document. A good flow is to ask users to set a password.

I think the most confusing thing for many normal people is that Google means e-mail to them, even though they have hotmail accounts.

Whoops, I didn’t read your problem correctly.

This is a bit more complicated then, thanks to the flow of oauth

My suggestion is to set a flag to keep track of if this is a login or signup attempt, pass the flag through to the server in the accounts-oauth package:

    const isSignup = getFlagSomehow();
    Accounts.callLoginMethod({
      methodArguments: [{oauth: { credentialToken, credentialSecret, isSignup }}],
      userCallback: callback && (err => callback(convertError(err))),
    });

Then on the server, check options.oauth.isSignup before doing the same test as suggested in the previous answer

Setting the flag might be annoying, since some oauth services redirect the whole page to get user permission, in which case you can probably use OAuth.saveDataForRedirect and OAuth.getDataAfterRedirect to save the flag

I’ve run into this exact same issue a couple years ago and this is basically how I fixed it, although I don’t use the higher-level accounts-oauth package so I had to register a new login handler to which I pass a parameter, when calling Accounts.callLoginMethod, to specify if I’m in a login or signup flow. I really think it’s the best solution here.

Thanks all for your suggestions … this has given me something to work with. I’ll try some of this stuff and report back.

Update: I did as @coagmano suggested and my problem is solved. To be exact:

I’ve copied the accounts-oath package and made the following changes:

In oauth_client.js the tryLoginAfterPopupClosed method looks like:

Accounts.oauth.tryLoginAfterPopupClosed = (credentialToken, callback) => {
  const credentialSecret = OAuth._retrieveCredentialSecret(credentialToken) || null;
  const routeName=FlowRouter.current().route.name;
  Accounts.callLoginMethod({
    methodArguments: [{oauth: { credentialToken, credentialSecret, routeName}}],
    userCallback: callback && (err => callback(convertError(err))),
    });
};

And in oauth_server.js I inserted this before the final call to Accounts.updateOrCreateUserFromExternalService:

    if (options.oauth.routeName=='signin') {
      if (!Accounts.findUserByEmail(result.serviceData.email)) {
        return {
          type: "oauth",
          error: new Meteor.Error(403,"No matching user found")
        };
      }
    }

Method parameters are checked so I had to modify the check at the top of the file:

  check(options.oauth, {
    credentialToken: String,
    credentialSecret: Match.OneOf(null, String),
    routeName: Match.Maybe(String)
  });
1 Like