locked
ASP.net Identity v2.0.0 Email Confirmation/Reset Password Errors RRS feed

  • Question

  • User155159196 posted

    We just launched our web app 6 days ago and have had over 5000 people register for accounts, great!

    The issue is 3-5% of those users who click the confirmation email link (and same seems to go for reset password) get shown the error screen presumably because they have an invalid token. 

    I read on StackOverflow that you need to encode the url to avoid special characters throwing it off and then decode it right before you validate it. I did that to no effect though, some users were still getting errors on their validation token.

    I also read that having different MachineKey's could be a reason tokens aren't processed as being valid. Everything is hosted on Azure so I presumed (and saw on SO) it was or should taken care of

    So with 30-50 people emailing us for the past 6 days now about issues, I got desperate while I tried to come up wit a solution and set my confirmEmail action to be the following:

    [AllowAnonymous]
            public ActionResult ConfirmEmail(string userId = null, string code = null)
            {
                if (userId == null || code == null)
                {
                    return View("Error");
                }
                else
                {
                    var emailCode = UserManager.GenerateEmailConfirmationToken(userId);
                    var result = UserManager.ConfirmEmail(userId, emailCode);
                    return View(result.Succeeded ? "ConfirmEmail" : "Error");
                }   
            }


    I thought to myself there is no way in heck this could fail, it literally just generates a token and then immediately uses it - yet somehow it still fails (by fails I mean the user sees the error page)

    My best guess as to a possible solution so far is this answer from SO (http://stackoverflow.com/questions/25405307/asp-net-identity-2-invalid-token-error, halfway down)

    Every time when a UserManager is created (or new-ed), a new dataProtectionProvider is generated as well. So when a user receives the email and clicks the link the AccountController is already not the old one, neither are the _userManager and it's token provider. So the new token provider will fail because it has no that token in it's memory. Thus we need to use a single instance for the token provider.

    Is that really the issue? If so, what the heck ASP.net Identity team? Why? 

    Some of the things I have changed:

    The default Register Action recommends sending confirmation emails the following way:

    var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);

    Where I have the following in my Register Action

    string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your XXXXXXX account");

    And then I specify the following in the SendEmailConfirmationTokenAsync

    private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
            {
                string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
                var callbackUrl = Url.Action("ConfirmEmail", "Account",
                   new { userId = userID, code = code }, protocol: Request.Url.Scheme);
    
    		// construct nice looking email body
    
        		await UserManager.SendEmailAsync(userID, subject, htmlBody);
    
                	return callbackUrl;
    	}

    To me, both sections are equivalent, is this not the case?

    And then the only other thing I can think of is how I added my db class, but that shouldn't affect the UserManager should it?

    The top part of my account controller looks like the following:

    private SiteClasses db = new SiteClasses();
    
            public AccountController()
            {
            }
    
            public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
            {
                UserManager = userManager;
                SignInManager = signInManager;
            }
    
            private ApplicationUserManager _userManager;
            public ApplicationUserManager UserManager
            {
                get
                {
                    return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
                }
                private set
                {
                    _userManager = value;
                }
            }
    
    	...

    We are using the recommended email provider sendgrid and I have never been able to replicate this issue (after creating ~60 test accounts manaually) and most people seem to get along fine.

    Part of this could be errors resulting from the users themselves, but this seems to be happening for a bunch of non tech-savvy people who just click on the link and expect it to work like a normal confirmation email should. Most users are coming to us from their iPhone where I would presume they are just using Apple's default mail client but not positive. I don't know of any spam filter or email settings that would remove the query string values from links.

    I'm getting somewhat desperate for answers as I am trying to launch a great new startup but am getting hung up by ASP.net Identity technicalities or bugs or whatever is going on.

    Tuesday, December 15, 2015 12:08 AM

Answers

  • User-2057865890 posted

    Hi,shaun314

    Welcome to asp.net forum.

    Account Confirmation and Password Recovery with ASP.NET Identity

    This tutorial contains more details and will show you how to set up email for local account  confirmation and allow users to reset their forgotten password in ASP.NET  Identity.

    The following code shows the email confirmation method:

    GET: /Account/ConfirmEmail
    [AllowAnonymous]
    public async Task<ActionResult> ConfirmEmail(string userId, string code)
    {
       if (userId == null || code == null)
       {
          return View("Error");
       }
       IdentityResult result;
       try
       {
          result = await UserManager.ConfirmEmailAsync(userId, code);
       }
       catch (InvalidOperationException ioe)
       {
          // ConfirmEmailAsync throws when the userId is not found.
          ViewBag.errorMessage = ioe.Message;
          return View("Error");
       }
    
       if (result.Succeeded)
       {
          return View();
       }
    
       // If we got this far, something failed.
       AddErrors(result);
       ViewBag.errorMessage = "ConfirmEmail failed";
       return View("Error");
    }

    To make your app more secure, ASP.NET Identity supports Two-Factor  authentication (2FA). 

    See ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authorization

    Best regards,

    Chris Zhao

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, December 22, 2015 7:44 AM