Adding Email Confirmation to SimpleMembership

In a previous post I described how to seed and customize the SimpleMembership provider that is now the default membership provider for an ASP.NET MVC 4 Internet application.  In this post we will extend what we learned from the previous post and incorporate an email confirmation step to the registration process.  SimpleMembership actually makes this very straight forward.  I will assume that you have followed the steps in my previous post and have already modified the UserProfile class to include an Email property.

First we need to modify the RegisterModel in AccountModels.cs to include an Email property for capturing the email address during registration.

public class RegisterModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    [Required]
    [Display(Name = "EMail")]
    public string Email { get; set; }

}



Then we need to modify the view in Register.cshtml to include our new Email property by adding these lines write after the ConfirmPassword.

   <li>
       @Html.LabelFor(m => m.Email)
       @Html.TextBoxFor(m => m.Email)
   </li>



Next we need to modify the existing AccountController to this.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        try
        {
            string confirmationToken = 
                WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { Email = model.Email }, true);
            dynamic email = new Email("RegEmail");
            email.To = model.Email;
            email.UserName = model.UserName;
            email.ConfirmationToken = confirmationToken;
            email.Send();

            return RedirectToAction("RegisterStepTwo", "Account");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

[AllowAnonymous]
public ActionResult RegisterStepTwo()
{
    return View();
}

[AllowAnonymous]
public ActionResult RegisterConfirmation(string Id)
{
    if (WebSecurity.ConfirmAccount(Id))
    {
        return RedirectToAction("ConfirmationSuccess");
    }
    return RedirectToAction("ConfirmationFailure");
}

[AllowAnonymous]
public ActionResult ConfirmationSuccess()
{
    return View();
}

[AllowAnonymous]
public ActionResult ConfirmationFailure()
{
    return View();
}

The Register action for the AccountController has been modified so that WebSecurity.CreateUserAndAccount now accepts the new Email property and update the UserProfile table with this information. It has also been modified to return a confirmation token. The boolean parameter tells the membership provider that a token confirmation process will be used.

The modified Register action sends an email to the user using the email address we captured during registration and the confirmation token that was generated by CreateUserAndAccount.  I am using Postal to handle sending the email. I like this package because it uses the Razor View Engine to generate your email. The view for my email look like this.

@{
    Layout = null;
}

To: @ViewBag.To
From: lolcats@website.com
Subject: Complete Registration Process

Hello @ViewBag.UserName,

To complete the registration process click on this link 
http://localhost/Account/RegisterConfirmation/@ViewBag.ConfirmationToken


Notice that when you test this out you will need to add a port number after localhost that matches where Visual Studio is running the application in debug mode.


For testing I use the default settings so that it sends the email to a directory that I can open and test the link for my confirmation page.  The default settings are in the web.config and they look like this.

  <system.net>
    <mailSettings>
      <smtp deliveryMethod="SpecifiedPickupDirectory">
        <specifiedPickupDirectory pickupDirectoryLocation="c:\email"/>
        <network host="localhost"/>
      </smtp>
    </mailSettings>
  </system.net>


Postal uses the SmtpClient to send the email and it uses these configuration settings. You can get more details on modifying these settings for an SMTP server on a network here. They would look something like this.

  <system.net>
    <mailSettings>
      <smtp>
        <network host="SMTPServer" port="" userName="username" password="password" />
      </smtp>
    </mailSettings>
  </system.net>


The Register action has also been modified to redirect to a RegisterStepTwo action which just provides a view that tells the user to look in their email for further instructions on the registration process. When you get to this page you can enter the link in your test email which uses the RegisterConfirmation action in the Account controller to confirm the user. It uses the WebSecurity.ConfirmAccount method which takes the confirmation token as a parameter.  If this is successful the user is redirected to the ConfirmationSuccess action which just presents a view indicating that they are confirmed and can now log into the system.

To test this out I modified the HomeController to use the AuthorizeAttribute for the Contact and About actions. It looks like this.

[Authorize(Roles = "Admin")]
public ActionResult About()
{
    ViewBag.Message = "Your app description page.";

    return View();
}

[Authorize]
public ActionResult Contact()
{
    ViewBag.Message = "Your contact page.";
    var context = new UsersContext();
    var username = User.Identity.Name;
    var user = context.UserProfiles.SingleOrDefault(u => u.UserName == username);
    ViewBag.Email = user.Email;

    return View();
}

I also modified the Contact action to add the users email to a ViewBag. This demonstrates how to retrieve information from the UserProfile table and display the information for verification.  The Contact view was modified to display the email with these view lines inserted in the body of the page.

 <p>
    <span class="label">Your Email:</span>
    <span><a href="mailto:@ViewBag.Email">@ViewBag.Email</a></span>
 </p>

If you try to access the Contact page before you log in you will be directed to the log in page. Log in with the credentials you used during the registration and it will redirect you to the Contact page.  Now you will see the email that you used during registration displayed.  If you try to access the About page when you are logged in you will redirected to the log in page. That is because the Authroize attribute for this action indicates you must be in the Admin role.  In this sample the user is not assigned any roles during the registration/account creation process.  If you log in again with the account that we seeded SimpleMembership with it will redirect you to the About page.

That is all there is to adding email confirmation to an ASP.NET MVC 4 Internet application. SimpleMembership makes it pretty... well simple.  This also demonstrates why you might want to customize the SimpleMembership UserProfile and how to access this custom data.

You can download the complete project here.




Popular posts from this blog

Using Claims in ASP.NET Identity

Adding Email Confirmation to ASP.NET Identity in MVC 5

Customizing ASP.NET Identity in MVC 5