ASP.NET vNext Identity Part 4 – Registration Confirmation

In my last post I allowed people to register but the registration process was not very robust. The account controller just accepted whatever you put in. It didn’t even attempt to validate that your email was correct. Today I am going to correct that oversight and see about doing the normal confirmation. Firstly, I will validate that the email address is of the correct form, then I’ll wire up a process whereby the user has to confirm their email by clicking on a link in an email that is sent to them.

Let’s take a look at the HTTP Post controller for /Account/Register. I need to validate the email address first. To do this, I changed the top of the method as follows:

        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                Debug.WriteLine("Register: Validating Email Address");
                if (!IsValidEmail(model.Email))
                {
                    Debug.WriteLine(string.Format("Register: Email Address {0} is not valid"));
                    ModelState.AddModelError("", "Invalid email address");
                    return View(model);
                }

I’ve delegated the checking of the email form to another method in the same class:

        public bool IsValidEmail(string s)
        {
            if (string.IsNullOrEmpty(s))
                return false;

            // Return true if strIn is in valid e-mail format.
            try
            {
                return Regex.IsMatch(s, @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$",
                      RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
            }
            catch (RegexMatchTimeoutException)
            {
                return false;
            }
        }

That long regular expression is from Microsoft and validates the email address. The method returns true or false depending on whether the string matches or not.

I now need to generate a link that the user can utilize to validate their email address. To do this, I change the POST /Account/Register routine again:

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                Debug.WriteLine("Register: Validating Email Address");
                if (!IsValidEmail(model.Email))
                {
                    Debug.WriteLine(string.Format("Register: Email Address {0} is not valid"));
                    ModelState.AddModelError("", "Invalid email address");
                    return View(model);
                }

                Debug.WriteLine("Register: Creating new ApplicationUser");
                var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
                Debug.WriteLine(string.Format("Register: New Application User = {0}", user.UserName));
                var result = await UserManager.CreateAsync(user, model.Password);
                Debug.WriteLine(string.Format("Register: Registration = {0}", result.Succeeded));
                if (result.Succeeded)
                {
                    Debug.WriteLine("Register: Sending Email Code");
                    var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
                    Debug.WriteLine(string.Format("Register: Email for code {0} is {1}", model.Email, code));
                    var callBackUrl = Url.Action("ConfirmEmail", "Account",
                        new { userId = user.Id, code = code },
                        protocol: Context.Request.Scheme);
                    await SendEmailAsync(model.Email,
                        "Confirm your account",
                        "Please confirm your account by clicking this link: <a href=\"" + callBackUrl + "\">link</a>");
                    ViewBag.Link = callBackUrl;
                    return View("RegisterEmail");
                }
                foreach (var error in result.Errors)
                {
                    Debug.WriteLine(string.Format("Register: Adding Error: {0}:{1}", error.Code, error.Description));
                    ModelState.AddModelError("", error.Description);
                }
                return View(model);
            }
            // Somethign went wrong, but we don't know what
            return View(model);
        }

When the user is created successfully, I use the Identity Framework to generate an email confirmation token. I then generate a Url for the request and send it via email. There is a flip side of this – the /Account/ConfirmEmail action is run when the user clicks on the link:

        /**
         * GET: /Account/ConfirmEmail
         */
        [HttpGet]
        [AllowAnonymous]
        public async Task<IActionResult> ConfirmEmail(string userId, string code)
        {
            Debug.WriteLine("ConfirmEmail: Checking for userId = " + userId);
            if (userId == null || code == null)
            {
                Debug.WriteLine("ConfirmEmail: Invalid Parameters");
                return View("ConfirmEmailError");
            }
            Debug.WriteLine("ConfirmEmail: Looking for userId");
            var user = await UserManager.FindByIdAsync(userId);
            if (user == null)
            {
                Debug.WriteLine("ConfirmEmail: Could not find user");
                return View("ConfirmEmailError");
            }
            Debug.WriteLine("ConfirmEmail: Found user - checking confirmation code");
            var result = await UserManager.ConfirmEmailAsync(user, code);
            Debug.WriteLine("ConfirmEmail: Code Confirmation = " + result.Succeeded.ToString());
            return View(result.Succeeded ? "ConfirmEmail" : "ConfirmEmailError");
        }

This method looks up the user and then checks with the Identity Framework (specifically the user manager) to see if the code is correct. It it’s correct, then the user gets a friendly “Please log in” notification. If not, an error message is generated.

I now need three views:

  1. Message that an email has been sent
  2. Message confirming successful activation
  3. Message indicating failed activation

I’ve created three crude views as follows:

RegisterEmail.cshtml

@{
    Layout = "~/Views/Layout/LoginPage.cshtml";
    ViewBag.Title = "Check Your Email";
}

<div class="login-form-outer">
    <h4>Check your email</h4>
    <p>
        We just tried to send you an email with an activation link.  When you get the email, click
        the link in the email to activate your account.
    </p>
</div>

ConfirmEmail.cshtml

@{
    Layout = "~/Views/Layout/LoginPage.cshtml";
    ViewBag.Title = "Confirm Email";
}

<div class="login-form-outer">
    <h4>Confirmation Successful</h4>
    <p>
        Thank you for confirming your email.
        Please @Html.ActionLink("click here to log in", "Login", "Account", routeValues: null)
    </p>
</div>

ConfirmEmailError.cshtml

@{
    Layout = "~/Views/Layout/LoginPage.cshtml";
    ViewBag.Title = "Confirm Email: Error";
}

<div class="login-form-outer">
    <h4>Confirmation Failed</h4>
    <p>
        We could not confirm your account.
    </p>
</div>

The project still won’t compile. That’s because I need a method in the AccountController for sending email async. I’ve got the following initially just to test things out:

        public static Task SendEmailAsync(string email, string subject, string message)
        {
            Debug.WriteLine("SendEmailAsync: " + message);
            return Task.FromResult(0);
        }

This version outputs the link that the user would be sent in the Debug Output window. I can now run the project and go through the process of registration. When it comes time to actually send the link, I can cut-and-paste it from the Debug Output window into my browser.

Sending Email via outlook.com

I have to pick an email provider for this next bit. I have an account on outlook.com so I will use that. The email settings for outlook.com are written in their site (see the settings for IMAP and SMTP) and are listed here:

  1. Host: smtp-mail.outlook.com
  2. Port: 587 or 25
  3. Authentication: Yes
  4. Security: TLS
  5. Username: Your email address
  6. Password: Your password

I’ve created a new section in the config.json file according to this specification:

"Email": {
"From": "Your-Account@outlook.com",
"Host": "smtp-mail.outlook.com",
"Port": "587",
"Security": "TLS",
"Username": "Your-Account@outlook.com",
"Password": "Your-Password"
}

Outlook.com doesn’t allow you to send generic emails through their system so the From: has to be your username. If you are using other systems, this may not be a restriction.

I’ve created a new namespace called AspNetIdentity.Services and created a new class called EmailService in there. First task is to take in a Configuration object and store the email data for later. I’m going to use a standard Singleton pattern for this. A Singleton pattern basically tells the system to create the object once and then return the same instance whenever asked. This means I can set the data once in the Startup routine and then access the same data all over the place. A typical Singleton pattern is like this:

        private static EmailService instance;

        private EmailService() { }

        public static EmailService
        {
            get
            {
                if (instance == null)
                {
                    instance = new EmailService();
                }
                return instance;
            }
        }

I’ve also created a bunch of properties and a SetConfiguration() routine that allows the startup object to set the configuration for me. Since it’s a lot of repetitive code for each property, I won’t repeat it here. You can check it out in the files on GitHub. I’ve added a single line to the Startup.cs ConfigureServices() method.

            // Configure the Email Service
            EmailProvider.Instance.SetConfiguration(Configuration);

Finally, I’ve removed the SendEmailAsync() method in the AccountControler and changed the call to SendEmailAsync() in the Register() post function to be:

                    try {
                        await EmailService.Instance.SendEmailAsync(model.Email,
                            "Confirm your account",
                            "Please confirm your account by clicking this link: <a href=\"" + callBackUrl + "\">link</a>");
                        ViewBag.Link = callBackUrl;
                        return View("RegisterEmail");
                    }
                    catch (SmtpException ex)
                    {
                        Debug.WriteLine("Could not send email: " + ex.InnerException.Message);
                        ModelState.AddModelError("", "Could not send email");
                        return View(model);
                    }

Now that I’ve got my configuration in the right place, I can concentrate on the task at hand – sending an email asynchronously with all the configuration. I’m going to do this using the standard System.Net.Mail.SmtpClient class:

        public Task SendEmailAsync(string email, string subject, string message)
        {
            if (!this.IsConfigured)
            {
                Debug.WriteLine("EmailService is not configured");
                Debug.WriteLine("SendEmailAsync: " + message);
                return Task.FromResult(0);
            }

            SmtpClient client = new SmtpClient(this.Hostname, this.Port);
            client.EnableSsl = this.Encryption.Equals("TLS");
            if (this.Authenticated) {
                client.Credentials = new System.Net.NetworkCredential(this.Username, this.Password);
            }

            MailAddress fromAddr = new MailAddress(this.FromAddress);
            MailAddress toAddr = new MailAddress(email);
            MailMessage mailmsg = new MailMessage(fromAddr, toAddr);
            mailmsg.Body = message + "\r\n";
            mailmsg.BodyEncoding = System.Text.Encoding.UTF8;
            mailmsg.Subject = subject;
            mailmsg.SubjectEncoding = System.Text.Encoding.UTF8;

            Debug.WriteLine("SendEmailAsync: Sending email to " + email);
            Debug.WriteLine("SendEmailAsync: " + message);
            return client.SendMailAsync(mailmsg);
        }

Troubleshooting Tips

Did you get it to work straight away? I didn’t. There are a bunch of things that could go wrong but they are all tied up in the SmtpException that gets returned. I set a breakpoint on the Debug.WriteLine statement immediately after the catch(SmtpException). Examine the InnerException which is usually where the problem lies. Some interesting issues:

  1. Cannot connect to remote host: Indicates that port 25 is blocked generally. My ISP blocks outbound port 25, so I have to use port 587 instead.
  2. Mailbox is inaccessible: No authentication: This indicates that the authentication failed or that you didn’t include authentication in your settings.

I’ve included enough debugging with the files to indicate the problem. You can take a look at the files on my GitHub Repository.

Now I’ve got some final clean-up to do, which I’ll do before the next post. I want to clean up the CSS and display of the various error messages that come back so that they look nicer. I also want to disable the Register button and put a spinner up when I click on Register. Finally, I need to clean up the email message that goes out so that the link is clickable and the email looks good.

In the next post, I’ll look at what to do about forgotten passwords.