Confirming emails for existing users (email address used by someone else, social login)

Viewed 171

PART 1
I have the following scenario and I was wondering if I could get your thoughts:

  • Jack has account user1 with email test@test.com
  • Jack really doesn't have test@test.com or has misspelt it when registering
  • Jill comes along and logins with Facebook test@test.com

Questions:

  1. Does Jill just get "user1" account? because after all it is her email address?
  2. What happens to Jack? Has he just lost his account no way around it?

PART 2
I'm currently converting an old webforms app to asp.net core, and I've come across a workflow issue more than a bug. The old Webforms app never had verified email addresses. I want to the asp.net core upon login to send confirmation emails. The issue is that there could be a chance that some people have used fake emails, the email service no longer exists or been using another persons email address (maybe mistyped it).

Example
Person1 signed up an account Person1 with email test@test.com (can't verify that email)
Person2 wants to sign up with Person2 with email test@test.com (since they really own this email and they can verify it)

Questions 1) Person 1 can't verify their email, they won't be able to login.

2) Person2 can't register because email already taken, Person2 does a forget password and discovers its' being used by Person1 and is then able to login as Person1. This isn't ideal because it's not really their account

How can the above situations be handled?

2 Answers

You have the choice to define how this situation will be managed. Technically, it's all your choice, since the external authentication will be considered only like an external token. Here's the default implentation (or at least, how it's handled in my latest project, can't remember).

[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
    if (remoteError != null)
    {
        return RedirectToAction(nameof(Login));
    }
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        return RedirectToAction(nameof(Login));
    }

    // Sign in the user with this external login provider if the user already has a login.
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
    if (result.Succeeded)
    {
        _logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        var email = info.Principal.FindFirstValue(ClaimTypes.Email);
        var isEmailAlreadyPresent = await _userManager.FindByEmailAsync(email);

        if (isEmailAlreadyPresent == null)
        {
            var user = new User { UserName = email, Email = email };
            var createUserResult = await _userManager.CreateAsync(user);
            if (createUserResult.Succeeded)
            {
                createUserResult = await _userManager.AddLoginAsync(user, info);
                if (createUserResult.Succeeded)
                {
                    await _signInManager.SignInAsync(user, isPersistent: false);
                    _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
                    return RedirectToLocal(returnUrl);
                }
            }
        }
        else
        {
            var addLoginAsyncResult = await _userManager.AddLoginAsync(isEmailAlreadyPresent, info);
            if (!result.Succeeded)
            {
            }

            await _signInManager.SignInAsync(isEmailAlreadyPresent, isPersistent: false);
            _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
            return RedirectToLocal(returnUrl);
        }
    }

    return RedirectToLocal(returnUrl);
}

As you can see, it really depends on how you want to implement it. And if your question concern the best way to do it, well, depends of your philosophy. But are you sure that this situation will often happens, or is it a "just in case" situation?

I think the registration logic must contain additional steps,

  • Abuse reporting link: if the second user who owns the email will register and see that his email is already registered, there must be a link for abuse reporting.

  • Secret questions: another possibility is to use additional steps for password reset, like secret questions if it has beet set before, or questions about previous activities to ensure the one who is resetting the password is the account owner

Another point is to define a verification period for old registered users:

  • all registered users must be notified via email or internal messaging to verify their email addresses after application upgrade within a period of time.

  • Within this period the user must be able to use his account without restrictions.

  • The ones who cannot verify their emails due to inaccessibility to the email, they may be able to change the email address to a new one within the given period.

  • After verification period is over, unverified accounts can be suspended till it has been verified, then can be deleted if still not verified. This way you can clean the db from unused accounts as well.

Related