Multi-Step (Two-Factor) ASP.NET MVC 4 Registration with SMS using Twilio Cloud Communication and SimpleMembershipProvider for Increased User Validity

Some sites such as live.com, gmail.com will require a multi-step registration and/or forgot password workflows to validate you say you are. Having an opportunity working with the Twilio Cloud Communication Platform, exposed how easily this can be done with their Api’s.

So for this post, I wanted to illustrate the steps in getting your MVC 4 application wired up with multi-step registration process with SMS code verification leveraging Twilio. We will start from my last blog post with Seed Users and Roles with MVC 4, SimpleMembershipProvider, SimpleRoleProvider, EntityFramework 5 CodeFirst, and Custom User Properties.

Since we already gathered the user’s mobile number during registration, let’s go ahead and add a property/field “IsSmsVerified” and run EntityFramework’s migration command update-database -verbose (so we can see what commands are being issued to our database for the migration.

NuGet and install the Twilio.Mvc package.

Update our UserProfile entity with IsSmsVerified and SmsVerificationCode properties.


    [Table("UserProfile")]
    public class UserProfile
    {
        public UserProfile()
        {
            IsSmsVerified = false;
        }

        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Mobile { get; set; }
        [DefaultValue(false)]
        public bool IsSmsVerified { get; set; }
        public string SmsVerificationCode { get; set; }
    }

Update our Seed method so that we are not inserting nulls for the provisioned users.


#region

using System.Data.Entity.Migrations;
using System.Linq;
using System.Web.Security;
using MVC4SimpleMembershipCodeFirstSeedingEF5.Models;
using WebMatrix.WebData;

#endregion

namespace MVC4SimpleMembershipCodeFirstSeedingEF5.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration<UsersContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(UsersContext context)
        {
            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName", autoCreateTables: true);

            if (!Roles.RoleExists("Administrator"))
                Roles.CreateRole("Administrator");

            if (!WebSecurity.UserExists("lelong37"))
                WebSecurity.CreateUserAndAccount(
                    "lelong37",
                    "password",
                    new {Mobile = "+19725000374", IsSmsVerified = false});

            if (!Roles.GetRolesForUser("lelong37").Contains("Administrator"))
                Roles.AddUsersToRoles(new[] {"lelong37"}, new[] {"Administrator"});
        }
    }
}

Run: update-database -verbose from the Package Manager Console

Now the fun begins, let’s update our AccountController.

  • Update the Register(RegisterModel model) Action and introduce the second step registration process of entering an SMS verfication code that we send the user using Twilio’s REST Api Client.

    Note: We are just scratching the tip of the ice berg in terms of what the Twilio Cloud Communication offers, you can visit their docs site for more info.

  • Add SmsVerification() Action, so that the user can enter the SMS verification code.
  • Add SmsVerication(SmsVerificationModel smsVerificationModel) Action, so that we can validate the user, the user’s mobile number, and SMS verification code.
  • Add GenerateSimpleSmsVerificationCode() method, a simple static helper method to generate a six character SMS verification code.

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                try
                {
                    var smsVerificationCode =
                        GenerateSimpleSmsVerificationCode();

                    WebSecurity.CreateUserAndAccount(
                        model.UserName,
                        model.Password,
                        new
                            {
                                model.Mobile,
                                IsSmsVerified = false,
                                SmsVerificationCode = smsVerificationCode
                            },
                        false);

                    var twilioRestClient = new TwilioRestClient(
                        ConfigurationManager.AppSettings.Get("Twilio:AccoundSid"),
                        ConfigurationManager.AppSettings.Get("Twilio:AuthToken"));

                    twilioRestClient.SendSmsMessage(
                        "+19722001298",
                        model.Mobile,
                        string.Format(
                            "Your ASP.NET MVC 4 with Twilio " +
                            "registration verification code is: {0}",
                            smsVerificationCode)
                        );

                    Session["registrationModel"] = model;

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

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

        [AllowAnonymous]
        public ActionResult SmsVerification()
        {
            return View(new SmsVerificationModel
                {
                    Username =
                        ((RegisterModel) Session["registrationModel"])
                            .UserName
                });
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult SmsVerification(SmsVerificationModel smsVerificationModel)
        {
            if (ModelState.IsValid)
            {
                var userContext = new UsersContext();

                var userProfile = userContext.UserProfiles
                    .Single(u => u.UserName == smsVerificationModel.Username);

                var registerModel = ((RegisterModel) Session["registrationModel"]);

                if (userProfile.SmsVerificationCode == smsVerificationModel.SmsVerificationCode)
                {
                    WebSecurity.Login(userProfile.UserName, registerModel.Password);
                    return RedirectToAction("Index", "Home");
                }
            }

            ModelState.AddModelError("", "The SMS verfication code was incorrect.");
            return RedirectToAction("SmsVerification", "Account");
        }

        private static string GenerateSimpleSmsVerificationCode()
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            var random = new Random();
            return new string(
                Enumerable.Repeat(chars, 6)
                    .Select(s => s[random.Next(s.Length)])
                    .ToArray());
        }

We could combine the two actions SmsVerication() and SmsVerication(SmsVerificationModel smsVerificationModel) into one, by checking the request verb for GET or Post, however for separation of concerns we will keep them “nice” and “separate”.

Let’s add some AppSettings entries to store our Twilio Rest Api credentials.


  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="PreserveLoginUrl" value="true" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="Twilio:AccoundSid" value="youtwilioaccountid" />
    <add key="Twilio:AuthToken" value="yourtwilioauthtoken" />
  </appSettings>

Note: Your Twilio credentials for using their REST Api can be found on your dashboard after registering.

Create a SmsVerification ViewModel.


using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations;

namespace MVC4SimpleMembershipCodeFirstSeedingEF5.Models
{
    public class SmsVerificationModel
    {
        [Display(Name = "Username")]
        public string Username { get; set; }

        [Required]
        [Display(Name = "SMS Verification Code")]
        public string SmsVerificationCode { get; set; }
    }
}

Let’s create the SmsVerification View where a user can input the SMS verification code that we sent to the user bound to the ViewModel we just created.

@model MVC4SimpleMembershipCodeFirstSeedingEF5.Models.SmsVerificationModel
@{
    //ViewBag.Title = "SMS Verification with MVC 4 & Twilio";
    ViewBag.Title = "SmsVerification";
}

<hgroup class="title">
    <h1>@ViewBag.Title.<br/></h1>
    <h3>Please enter your SMS verification code to complete registration.</h3>
</hgroup>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

    @Html.HiddenFor(m => m.Username)

    <fieldset>
        <legend>SMS Verifcation Form</legend>
        <ol>
            <li>
                @Html.LabelFor(m => m.Username)
                @Html.DisplayTextFor(m => m.Username) 
                <br/><br/>
            </li>
            <li>
                @Html.LabelFor(m => m.SmsVerificationCode)
                @Html.TextBoxFor(m => m.SmsVerificationCode)
            </li>
        </ol>
        <input type="submit" value="SmsVerification" />
    </fieldset>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Step 1 of the registration process, run the application and register.

For a quick sanity check let’s just make sure our SimpleMembershipProvider is persisting the extra properties we added earlier e.g. SmsVerificationCode, IsSmsVerified.


SELECT TOP 1000 [UserId]
      ,[UserName]
      ,[Mobile]
      ,[IsSmsVerified]
      ,[SmsVerificationCode]
  FROM [aspnet-MVC4SimpleMembershipCodeFirstSeedingEF5].[dbo].[UserProfile]

Good, we can see here that Mobile, IsSmsVerified and SmsVerificationCode is being saved when we invoked the WebSecurity.CreateUserAndAccount method earlier from our Registration Action.


                    WebSecurity.CreateUserAndAccount(
                        model.UserName,
                        model.Password,
                        new
                            {
                                model.Mobile,
                                IsSmsVerified = false,
                                SmsVerificationCode = smsVerificationCode
                            },
                        false);

Step 2, SMS notification to the user’s mobile number was received with the SMS verification code.

Step 3 of the registration process, input the SMS verification code in the SMSVerfication View.

You have now successfully completed the 3 step registration process and have been automatically logged into the site!

Now there are obviously TODO’s here, you can create an new authorize Attribute to verify that the IsSmsVerified property for the user is not false, clean up how we are storing the RegisterModel in session, additional bullet proofing the app in terms of security gaps, etc.. However the emphasis of this blog was multi-step registration to for increased validity of the user.

Last but not least, you can use the a similar implementation for things like forgot password or any other type of workflow that needs that extra degree of validation.

Happy Coding…! 🙂

Download sample application: https://skydrive.live.com/redir?resid=949A1C97C2A17906!2383

Seed Users and Roles with MVC 4, SimpleMembershipProvider, SimpleRoleProvider, Entity Framework 5 CodeFirst, and Custom User Properties

I’ve been Googling over the weekend and so far didn’t find any articles out there on how integrate EF5 CodeFirst nicely with SimpleMembership and at the same time, seeding some of your users, roles and associating users to roles while supporting custom fields/properties during registration, hence this blog post.

I think this is a nice to have, especially during PoC development where you could be developing features that depend on authentication and authorization while making schema changes with EF CodeFirst. The last thing you want to do is run update-database for migrations and have to manually re-insert/re-seed all your users, roles and associating the two every time you ran migrations (e.g. update-database -force from the Package Manager Console).

First, create an “Internet Application” ASP.NET MVC4 Project, because this is the only out of the box MVC template that has the new SimpleMembershipProvider wired up out of the box. One of the features I like the most about the SimpleMembershipProvider is it gives you total control of the highly requested “User” table/entity. Meaning you integrate SimpleMembershipProvider with your own user table, as long as it has a UserId and UserName fields in your table.

Obviously there are many more features in SimpleMembership provider, here are some links in this regard:

Explicitly wire up the providers even though this is implied, so that when do run the “update-database” command from the Package Manager Console for migrations we can use the native “Roles” Api.

In the “System.Web” Section add:


    &lt;roleManager enabled=&quot;true&quot; defaultProvider=&quot;SimpleRoleProvider&quot;&gt;
      &lt;providers&gt;
        &lt;clear/&gt;
        &lt;add name=&quot;SimpleRoleProvider&quot; type=&quot;WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData&quot;/&gt;
      &lt;/providers&gt;
    &lt;/roleManager&gt;
    &lt;membership defaultProvider=&quot;SimpleMembershipProvider&quot;&gt;
      &lt;providers&gt;
        &lt;clear/&gt;
        &lt;add name=&quot;SimpleMembershipProvider&quot; type=&quot;WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData&quot; /&gt;
      &lt;/providers&gt;
    &lt;/membership&gt;

Let’s add a custom field to the User table by adding a Mobile property to the UserProfile entity (MVC4SimpleMembershipCodeFirstSeedingEF5/Models/AccountModel.cs).


    [Table(&quot;UserProfile&quot;)]
    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Mobile { get; set; }
    }

Enable EF5 CodeFirst Migrations

Seed your Roles and any Users you want to provision, also note the WebSecurity.InitializeDatabaseConnection method we are invoking. This method is what tells SimpleMembership which table to use when working with Users and which columns are for the UserId and UserName. I’m also going to demonstrate how you can hydrate additional custom columns such as requiring a User’s mobile number when registering on the site.


#region

using System.Data.Entity.Migrations;
using System.Linq;
using System.Web.Security;
using MVC4SimpleMembershipCodeFirstSeedingEF5.Models;
using WebMatrix.WebData;

#endregion

namespace MVC4SimpleMembershipCodeFirstSeedingEF5.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration&lt;UsersContext&gt;
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
        }

        protected override void Seed(UsersContext context)
        {
            WebSecurity.InitializeDatabaseConnection(
                &quot;DefaultConnection&quot;,
                &quot;UserProfile&quot;,
                &quot;UserId&quot;,
                &quot;UserName&quot;, autoCreateTables: true);

            if (!Roles.RoleExists(&quot;Administrator&quot;))
                Roles.CreateRole(&quot;Administrator&quot;);

            if (!WebSecurity.UserExists(&quot;lelong37&quot;))
                WebSecurity.CreateUserAndAccount(
                    &quot;lelong37&quot;,
                    &quot;password&quot;,
                    new {Mobile = &quot;+19725000000&quot;});

            if (!Roles.GetRolesForUser(&quot;lelong37&quot;).Contains(&quot;Administrator&quot;))
                Roles.AddUsersToRoles(new[] {&quot;lelong37&quot;}, new[] {&quot;Administrator&quot;});
        }
    }
}

Now, run the update-database -verbose command from Package Manager Console, we are using the -verbose switch so that we can get better visibility on what’s getting executed on SQL. Notice the Mobile field is being created.

Let’s go ahead and do a sanity check and make sure all of our Users and Roles were provisioned correctly from the Seed method in our migration configuration, by executing a few queries.


SELECT TOP 1000 [UserId]
      ,[UserName]
      ,[Mobile]
  FROM [aspnet-MVC4SimpleMembershipCodeFirstSeedingEF5].[dbo].[UserProfile]
  
  SELECT TOP 1000 [RoleId]
      ,[RoleName]
  FROM [aspnet-MVC4SimpleMembershipCodeFirstSeedingEF5].[dbo].[webpages_Roles]
  
  SELECT TOP 1000 [UserId]
      ,[RoleId]
  FROM [aspnet-MVC4SimpleMembershipCodeFirstSeedingEF5].[dbo].[webpages_UsersInRoles]

Results

  • Users were inserted
  • Roles were provisioned
  • The user “LeLong37” was added and associated to the Administrator role

Finally for a sanity check, let’s go ahead and run the app and sign-in with the provisioned user from our Seed method.

Successfully authenticated with our seeded provisioned user (thought I’d add a blue star badge to the screenshot to add some humor 😛 )!

One last thing, let’s go ahead and modify our Register view, Register model and AccountController to gather the user’s mobile number during registration.

Register View (Register.cshtml)


@model MVC4SimpleMembershipCodeFirstSeedingEF5.Models.RegisterModel
@{
    ViewBag.Title = &quot;Register&quot;;
}

&lt;hgroup class=&quot;title&quot;&gt;
    &lt;h1&gt;@ViewBag.Title.&lt;/h1&gt;
    &lt;h2&gt;Create a new account.&lt;/h2&gt;
&lt;/hgroup&gt;

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

    &lt;fieldset&gt;
        &lt;legend&gt;Registration Form&lt;/legend&gt;
        &lt;ol&gt;
            &lt;li&gt;
                @Html.LabelFor(m =&gt; m.UserName)
                @Html.TextBoxFor(m =&gt; m.UserName)
            &lt;/li&gt;
            &lt;li&gt;
                @Html.LabelFor(m =&gt; m.Password)
                @Html.PasswordFor(m =&gt; m.Password)
            &lt;/li&gt;
            &lt;li&gt;
                @Html.LabelFor(m =&gt; m.ConfirmPassword)
                @Html.PasswordFor(m =&gt; m.ConfirmPassword)
            &lt;/li&gt;
            &lt;li&gt;
                @Html.LabelFor(m =&gt; m.Mobile)
                @Html.TextBoxFor(m =&gt; m.Mobile)
            &lt;/li&gt;
        &lt;/ol&gt;
        &lt;input type=&quot;submit&quot; value=&quot;Register&quot; /&gt;
    &lt;/fieldset&gt;
}

@section Scripts {
    @Scripts.Render(&quot;~/bundles/jqueryval&quot;)
}


Register model (AccountModel.cs)


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

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

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

        [Required]
        [DataType(DataType.PhoneNumber)]
        [Display(Name = &quot;Mobile&quot;)]
        public string Mobile { get; set; }
    }


Register Action (AccountController.cs)


        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                try
                {
                    WebSecurity.CreateUserAndAccount(
                        model.UserName, 
                        model.Password, 
                        new { Mobile = model.Mobile }, 
                        false);

                    WebSecurity.Login(model.UserName, model.Password);
                    return RedirectToAction(&quot;Index&quot;, &quot;Home&quot;);
                }
                catch (MembershipCreateUserException e)
                {
                    ModelState.AddModelError(&quot;&quot;, ErrorCodeToString(e.StatusCode));
                }
            }

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

Finally, let’s register.

Let’s go ahead and run our SQL queries again and make sure the mobile number was actually saved to our UserProfile table during the registration.

Sweet! Registration successful, with mobile number saved to the UserProfile table.

Happy Coding…!

Download Sample Application: http://blog.longle.net/2012/09/26/multi-step-asp-net-mvc-4-registration-with-sms-using-twilio-cloud-communication-and-simplemembershipprovider-for-increased-user-validity/