2

We just launched our ASP.net MVC (using Identity 2.0) 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 here 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 with 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 (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.

But this is no longer necessary with all the OWIN stuff, right?

Is that really the issue still? 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 (which is how the provided example from MS came + adding my database):

private readonly 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 personally never been able to replicate this issue (after creating ~60 test accounts manually) 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.

Advice on where to look or how to set up things would be greatly appreciated

Community
  • 1
  • 1
Shaun314
  • 3,191
  • 4
  • 22
  • 27
  • I appreciate you have taken a lot of time to write the question. But you missed the most important part. You didn't mention the error that is shown. A single stack trace of that one could have been helpful than all that details. – brainless coder Dec 15 '15 at 03:57
  • Their is no stack trace, exception, or anything. It's simply an invalid token for a reason I can't figure out - If there was a stack trace, trust me, it'd be on here and probably already debugged on my own if that was the case :) – Shaun314 Dec 15 '15 at 19:19
  • hello, having the same issue here, any updates about this thread? – Benzara Tahar Oct 18 '16 at 11:24
  • I'm currently dealing with the same issue. Any updates? – rsnyder Nov 08 '18 at 22:55

2 Answers2

0

I send the email as part of the URL currently.

We have a customer with a forwarding email account at tufts.edu going to gmail.com and it must be rewriting her email address that is part of the URL - which is HORRENDOUS but I can't see what else it is possibly doing.

This is one more thing to be aware of if I can confirm this.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
0

I ended up confirming the email manually in the AspNetUsers table instead of creating a new token on the fly and trying to use UserManager.ConfirmEmail.

[HandleError(ExceptionType = typeof(HttpException), View = "ConfirmEmail")]
public ActionResult ConfirmEmail(string userId = null, string code = null)
    {
        if (userId == null || code == null)
        {
            return View("Error");
        }
        var result = await UserManager.ConfirmEmailAsync(userId, code);
        if (result.Succeeded)
        {
            return View("ConfirmEmail");
        }
        else
        {
            var user = DBContext.Users.Find(userId);
            user.EmailConfirmed = true;
            DBContext.SaveChanges();
            throw new HttpException(result.Errors.FirstOrDefault());
        } 


    }

I also used [HandleError(ExceptionType = typeof(HttpException), View = "ConfirmEmail")] to still log the error but direct the use to the ConfirmEmail page still.

Not a great solution but I haven't been able to find anything to fix this issue.

rsnyder
  • 383
  • 1
  • 6
  • 19