Account Verification with Authy, C# and ASP.NET MVC

Ready to implement user account verification in your application? Here's how it works at a high level:

  1. The user begins the registration process by entering their data, including a phone number, into a signup form.
  2. The authentication system sends a one-time password to the user's mobile phone to verify the possession of that phone number.
  3. The user enters the one-time password into a form before completing registration.
  4. The user opens a success page and receives an SMS indicating that their account has been created.

Building Blocks

To get this done, you'll be working with the following Twilio-powered APIs:

Authy REST API

  • Authy Docs: Find quick starts, documentation, and all about the helper libraries.

Twilio REST API

  • Messages Resource: We will use Twilio directly to send our user a confirmation message after they have created an account.
Loading Code Samples...
Language
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
AccountVerification.Web/App_Start/IdentityConfig.cs
Identity Configuration

AccountVerification.Web/App_Start/IdentityConfig.cs

All of this can be done in under a half an hour with the simplicity and power of Authy and Twilio.

The User Model

The User Model for this use-case is pretty straight-forward. If you have already read through the 2FA tutorial this one probably looks very similar.

Loading Code Samples...
Language
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;

namespace AccountVerification.Web.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Required]
        public string Name { get; set; }

        [Required]
        public string CountryCode { get; set; }

        public string AuthyUserId { get; set; }

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }
}
AccountVerification.Web/Models/IdentityModels.cs
Identity Model

AccountVerification.Web/Models/IdentityModels.cs

Pretty simple ApplicationUser model, right? Next, we're going to visit the registration form on the client side.

Registration Form

When we create a new user, we ask for a name, e-mail address, password and mobile number with a country code. Using Authy, we send a one-time password to this phone number via SMS to validate it.

It is now the controller's responsibility to verify that the user has provided the necessary information to create a new user. If the user is created successfully, they will be logged into the system automatically, leaving them logged in but not yet verified.

Loading Code Samples...
Language
using System;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;

namespace AccountVerification.Web.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private ApplicationSignInManager _signInManager;
        private ApplicationUserManager _userManager;

        public AccountController()
        {
        }

        public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
        {
            UserManager = userManager;
            SignInManager = signInManager;
        }

        public ApplicationSignInManager SignInManager
        {
            get
            {
                return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
            }
            private set 
            { 
                _signInManager = value; 
            }
        }

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        //
        // GET: /Account/VerifyCode
        [AllowAnonymous]
        public ActionResult VerifyRegistrationCode(string message)
        {
            ViewBag.Message = message;
            return View(new VerifyCodeViewModel());
        }

        //
        // POST: /Account/VerifyCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> VerifyRegistrationCode(VerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if(user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            var result = await UserManager.ConfirmPhoneNumberAsync(user.Id, model.Code);

            if (result.Succeeded)
            {
                await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
                await UserManager.SendSmsAsync(user.Id, ApplicationMessages.SignupComplete);
                return RedirectToAction("Status");
            }
            else
            {
                AddErrors(result);
                return View(model);
            }
        }

        //
        // GET: /Account/Status
        [AllowAnonymous]
        public ActionResult Status()
        {
            return View();
        }

        //
        // GET: /Account/ResendVerificationCode
        [AllowAnonymous]
        public ActionResult ResendVerificationCode(string email)
        {
            return View(new ResendVerifyCodeViewModel() {Email = email});
        }

        //
        // POST: /Account/ResendVerificationCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> ResendVerificationCode(ResendVerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if (user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);
            return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeResent});
        }

        //
        // GET: /Account/Register
        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }

        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser
                {
                    Name = model.Name,
                    UserName = model.Email,
                    Email = model.Email,
                    PhoneNumber = String.Format("+{0}{1}", model.CountryCode, model.PhoneNumber),
                    CountryCode = model.CountryCode,
                };
                var result = await UserManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    // For more information on how to enable account confirmation and password reset 
                    // please visit http://go.microsoft.com/fwlink/?LinkID=320771
                    // Send an email with this link
                    // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
                    // var callbackUrl = Url.Action("ConfirmEmail", "Account", 
                    //                  new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
                    // await UserManager.SendEmailAsync(user.Id, "Confirm your account", 
                    //                  "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");


                    await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);

                    return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeSent});
                }
                AddErrors(result);
            }

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

        //
        // POST: /Account/LogOff
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
            return RedirectToAction("Index", "Home");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_userManager != null)
                {
                    _userManager.Dispose();
                    _userManager = null;
                }

                if (_signInManager != null)
                {
                    _signInManager.Dispose();
                    _signInManager = null;
                }
            }

            base.Dispose(disposing);
        }

        #region Helpers
        // Used for XSRF protection when adding external logins
        private const string XsrfKey = "XsrfId";

        private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }

        private void AddErrors(IdentityResult result)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }

        internal class ChallengeResult : HttpUnauthorizedResult
        {
            public ChallengeResult(string provider, string redirectUri)
                : this(provider, redirectUri, null)
            {
            }

            public ChallengeResult(string provider, string redirectUri, string userId)
            {
                LoginProvider = provider;
                RedirectUri = redirectUri;
                UserId = userId;
            }

            public string LoginProvider { get; set; }
            public string RedirectUri { get; set; }
            public string UserId { get; set; }

            public override void ExecuteResult(ControllerContext context)
            {
                var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
        }

        #endregion
    }
}
AccountVerification.Web/Controllers/AccountController.cs
Register a new user with the account controller

AccountVerification.Web/Controllers/AccountController.cs

You've now seen how to register an application user. Next, let's take a minute to configure Authy use in our application.

Authy Configuration

Before we move on, let's take a quick look at some application configuration.

You need to create a Local.config file under the AccountVerification.Web directory with the following content:

<?xml version="1.0" encoding="utf-8"?>
<appSettings>
    <add key="TwilioAccountSID" value="your_twilio_account_SID" />
    <add key="TwilioAuthToken" value="your_twilio_auth_token" />
    <add key="TwilioNumber" value="your_twilio_number" />
    <add key="AuthyKey" value="your_authy_key" />
</appSettings>
 

Now we need our Authy production key (sign up for Authy here). When you create an Authy application, the production key can be found on the dashboard. 

Authy dashboard

Loading Code Samples...
Language
<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-AccountVerification.Web.mdf;Initial Catalog=aspnet-AccountVerification.Web;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings file="Local.config">
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="TwilioAccountSID" value="your_twilio_account_SID" />
    <add key="TwilioAuthToken" value="your_twilio_account_auth_token" />
    <add key="TwilioNumber" value="your_twilio_number" />
    <add key="AuthyKey" value="your_authy_key" />
  </appSettings>
  <!--
    For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

    The following attributes can be set on the <httpRuntime> tag.
      <system.Web>
        <httpRuntime targetFramework="4.5" />
      </system.Web>
  -->
  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="4" compilerOptions="/langversion:5 /nowarn:1659;1699;1701">
          <providerOption name="CompilerVersion" value="v4.0" />
      </compiler>
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
</configuration>
AccountVerification.Web/Web.config
Configure application to work with Twilio and Authy

AccountVerification.Web/Web.config

This configuration is pretty straightforward, right? Next, we need to jump over to the UserController to set up the Authy client and create an instance method to send a one-time password.

Sending a Token on Account Creation

Once the user has an authyId, we can send a verification code to that user's mobile phone.

When our user is created successfully via the form we implemented, we send an SMS with a token to the user's phone to verify their account in our controller. When the code is sent we redirect to another page where the user can enter the token they received, therefore completing the verification process.

Loading Code Samples...
Language
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
AccountVerification.Web/App_Start/IdentityConfig.cs
Send user an Authy token on account creation

AccountVerification.Web/App_Start/IdentityConfig.cs

Next, we'll learn how to verify the code the user has provided us during this step.

Verifying the Code

The Authy .NET client provides us with a VerifyToken() method that takes a userId and a token as arguments. In this case, we just need to check that the API request was successful and, if so, call setPhoneNumberConfirmedAsync passing true.

Loading Code Samples...
Language
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
AccountVerification.Web/App_Start/IdentityConfig.cs
Verify an Authy code

AccountVerification.Web/App_Start/IdentityConfig.cs

However, our verification form wouldn't be very usable if there wasn't a way to resend a verification code if the message didn't arrive at the end user's handset.

Re-sending the Verification Code

Since the form for re-sending the verification code is only one line, we're going to skip that for this tutorial. Let's just look at the controller function.

This controller loads the model associated with the request and then uses the same Authy API method we used earlier to resend the code.

Loading Code Samples...
Language
using System;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;

namespace AccountVerification.Web.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private ApplicationSignInManager _signInManager;
        private ApplicationUserManager _userManager;

        public AccountController()
        {
        }

        public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
        {
            UserManager = userManager;
            SignInManager = signInManager;
        }

        public ApplicationSignInManager SignInManager
        {
            get
            {
                return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
            }
            private set 
            { 
                _signInManager = value; 
            }
        }

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        //
        // GET: /Account/VerifyCode
        [AllowAnonymous]
        public ActionResult VerifyRegistrationCode(string message)
        {
            ViewBag.Message = message;
            return View(new VerifyCodeViewModel());
        }

        //
        // POST: /Account/VerifyCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> VerifyRegistrationCode(VerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if(user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            var result = await UserManager.ConfirmPhoneNumberAsync(user.Id, model.Code);

            if (result.Succeeded)
            {
                await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
                await UserManager.SendSmsAsync(user.Id, ApplicationMessages.SignupComplete);
                return RedirectToAction("Status");
            }
            else
            {
                AddErrors(result);
                return View(model);
            }
        }

        //
        // GET: /Account/Status
        [AllowAnonymous]
        public ActionResult Status()
        {
            return View();
        }

        //
        // GET: /Account/ResendVerificationCode
        [AllowAnonymous]
        public ActionResult ResendVerificationCode(string email)
        {
            return View(new ResendVerifyCodeViewModel() {Email = email});
        }

        //
        // POST: /Account/ResendVerificationCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> ResendVerificationCode(ResendVerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if (user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);
            return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeResent});
        }

        //
        // GET: /Account/Register
        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }

        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser
                {
                    Name = model.Name,
                    UserName = model.Email,
                    Email = model.Email,
                    PhoneNumber = String.Format("+{0}{1}", model.CountryCode, model.PhoneNumber),
                    CountryCode = model.CountryCode,
                };
                var result = await UserManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    // For more information on how to enable account confirmation and password reset 
                    // please visit http://go.microsoft.com/fwlink/?LinkID=320771
                    // Send an email with this link
                    // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
                    // var callbackUrl = Url.Action("ConfirmEmail", "Account", 
                    //                  new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
                    // await UserManager.SendEmailAsync(user.Id, "Confirm your account", 
                    //                  "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");


                    await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);

                    return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeSent});
                }
                AddErrors(result);
            }

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

        //
        // POST: /Account/LogOff
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
            return RedirectToAction("Index", "Home");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_userManager != null)
                {
                    _userManager.Dispose();
                    _userManager = null;
                }

                if (_signInManager != null)
                {
                    _signInManager.Dispose();
                    _signInManager = null;
                }
            }

            base.Dispose(disposing);
        }

        #region Helpers
        // Used for XSRF protection when adding external logins
        private const string XsrfKey = "XsrfId";

        private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }

        private void AddErrors(IdentityResult result)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }

        internal class ChallengeResult : HttpUnauthorizedResult
        {
            public ChallengeResult(string provider, string redirectUri)
                : this(provider, redirectUri, null)
            {
            }

            public ChallengeResult(string provider, string redirectUri, string userId)
            {
                LoginProvider = provider;
                RedirectUri = redirectUri;
                UserId = userId;
            }

            public string LoginProvider { get; set; }
            public string RedirectUri { get; set; }
            public string UserId { get; set; }

            public override void ExecuteResult(ControllerContext context)
            {
                var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
        }

        #endregion
    }
}
AccountVerification.Web/Controllers/AccountController.cs
Resend a verification code via .Net controller

AccountVerification.Web/Controllers/AccountController.cs

We're getting close now! Let's provide a pleasant user onboarding experience and send a confirmation message to our new user.

Sending the Confirmation Message

We create a single instance of the Twilio REST API helper, called twilio in this example.

Then all we need to do to send an SMS to the user's phone is use the built in SendMessage().

For more information on how to send SMS using Twilio's Rest API visit this link.

Loading Code Samples...
Language
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
AccountVerification.Web/App_Start/IdentityConfig.cs
Send verified user a confirmation message with Twilio

AccountVerification.Web/App_Start/IdentityConfig.cs

Congtratulations! You've just implemented account verification so your users can confirm their phone number.

Where to Next?

If you're a C# developer working with Twilio, you might want to check out these other tutorials:

Click-To-Call

Put a button on your web page that connects visitors to live support or sales people via telephone.

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

Did this help?

Thanks for checking this tutorial out! If you have any feedback to share with us, or just want to let us know what awesome thing you're building, we'd love to hear it. Reach out to us on Twitter!

Kevin Whinnery
Kat King
Agustin Camino
Hector Ortega
Jose Oliveros

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;

namespace AccountVerification.Web.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Required]
        public string Name { get; set; }

        [Required]
        public string CountryCode { get; set; }

        public string AuthyUserId { get; set; }

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }
}
using System;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;

namespace AccountVerification.Web.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private ApplicationSignInManager _signInManager;
        private ApplicationUserManager _userManager;

        public AccountController()
        {
        }

        public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
        {
            UserManager = userManager;
            SignInManager = signInManager;
        }

        public ApplicationSignInManager SignInManager
        {
            get
            {
                return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
            }
            private set 
            { 
                _signInManager = value; 
            }
        }

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        //
        // GET: /Account/VerifyCode
        [AllowAnonymous]
        public ActionResult VerifyRegistrationCode(string message)
        {
            ViewBag.Message = message;
            return View(new VerifyCodeViewModel());
        }

        //
        // POST: /Account/VerifyCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> VerifyRegistrationCode(VerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if(user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            var result = await UserManager.ConfirmPhoneNumberAsync(user.Id, model.Code);

            if (result.Succeeded)
            {
                await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
                await UserManager.SendSmsAsync(user.Id, ApplicationMessages.SignupComplete);
                return RedirectToAction("Status");
            }
            else
            {
                AddErrors(result);
                return View(model);
            }
        }

        //
        // GET: /Account/Status
        [AllowAnonymous]
        public ActionResult Status()
        {
            return View();
        }

        //
        // GET: /Account/ResendVerificationCode
        [AllowAnonymous]
        public ActionResult ResendVerificationCode(string email)
        {
            return View(new ResendVerifyCodeViewModel() {Email = email});
        }

        //
        // POST: /Account/ResendVerificationCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> ResendVerificationCode(ResendVerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if (user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);
            return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeResent});
        }

        //
        // GET: /Account/Register
        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }

        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser
                {
                    Name = model.Name,
                    UserName = model.Email,
                    Email = model.Email,
                    PhoneNumber = String.Format("+{0}{1}", model.CountryCode, model.PhoneNumber),
                    CountryCode = model.CountryCode,
                };
                var result = await UserManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    // For more information on how to enable account confirmation and password reset 
                    // please visit http://go.microsoft.com/fwlink/?LinkID=320771
                    // Send an email with this link
                    // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
                    // var callbackUrl = Url.Action("ConfirmEmail", "Account", 
                    //                  new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
                    // await UserManager.SendEmailAsync(user.Id, "Confirm your account", 
                    //                  "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");


                    await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);

                    return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeSent});
                }
                AddErrors(result);
            }

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

        //
        // POST: /Account/LogOff
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
            return RedirectToAction("Index", "Home");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_userManager != null)
                {
                    _userManager.Dispose();
                    _userManager = null;
                }

                if (_signInManager != null)
                {
                    _signInManager.Dispose();
                    _signInManager = null;
                }
            }

            base.Dispose(disposing);
        }

        #region Helpers
        // Used for XSRF protection when adding external logins
        private const string XsrfKey = "XsrfId";

        private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }

        private void AddErrors(IdentityResult result)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }

        internal class ChallengeResult : HttpUnauthorizedResult
        {
            public ChallengeResult(string provider, string redirectUri)
                : this(provider, redirectUri, null)
            {
            }

            public ChallengeResult(string provider, string redirectUri, string userId)
            {
                LoginProvider = provider;
                RedirectUri = redirectUri;
                UserId = userId;
            }

            public string LoginProvider { get; set; }
            public string RedirectUri { get; set; }
            public string UserId { get; set; }

            public override void ExecuteResult(ControllerContext context)
            {
                var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
        }

        #endregion
    }
}
<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301880
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-AccountVerification.Web.mdf;Initial Catalog=aspnet-AccountVerification.Web;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings file="Local.config">
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="TwilioAccountSID" value="your_twilio_account_SID" />
    <add key="TwilioAuthToken" value="your_twilio_account_auth_token" />
    <add key="TwilioNumber" value="your_twilio_number" />
    <add key="AuthyKey" value="your_authy_key" />
  </appSettings>
  <!--
    For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.

    The following attributes can be set on the <httpRuntime> tag.
      <system.Web>
        <httpRuntime targetFramework="4.5" />
      </system.Web>
  -->
  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="4" compilerOptions="/langversion:5 /nowarn:1659;1699;1701">
          <providerOption name="CompilerVersion" value="v4.0" />
      </compiler>
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
</configuration>
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}
using System;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;

namespace AccountVerification.Web.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private ApplicationSignInManager _signInManager;
        private ApplicationUserManager _userManager;

        public AccountController()
        {
        }

        public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
        {
            UserManager = userManager;
            SignInManager = signInManager;
        }

        public ApplicationSignInManager SignInManager
        {
            get
            {
                return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
            }
            private set 
            { 
                _signInManager = value; 
            }
        }

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        //
        // GET: /Account/VerifyCode
        [AllowAnonymous]
        public ActionResult VerifyRegistrationCode(string message)
        {
            ViewBag.Message = message;
            return View(new VerifyCodeViewModel());
        }

        //
        // POST: /Account/VerifyCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> VerifyRegistrationCode(VerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if(user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            var result = await UserManager.ConfirmPhoneNumberAsync(user.Id, model.Code);

            if (result.Succeeded)
            {
                await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
                await UserManager.SendSmsAsync(user.Id, ApplicationMessages.SignupComplete);
                return RedirectToAction("Status");
            }
            else
            {
                AddErrors(result);
                return View(model);
            }
        }

        //
        // GET: /Account/Status
        [AllowAnonymous]
        public ActionResult Status()
        {
            return View();
        }

        //
        // GET: /Account/ResendVerificationCode
        [AllowAnonymous]
        public ActionResult ResendVerificationCode(string email)
        {
            return View(new ResendVerifyCodeViewModel() {Email = email});
        }

        //
        // POST: /Account/ResendVerificationCode
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> ResendVerificationCode(ResendVerifyCodeViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                ModelState.AddModelError("", ApplicationMessages.UserNotFoundForGivenEmail);
                return View(model);
            }
            if (user.PhoneNumberConfirmed)
            {
                ModelState.AddModelError("", ApplicationMessages.UserAlreadyConfirmed);
                return View(model);
            }

            await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);
            return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeResent});
        }

        //
        // GET: /Account/Register
        [AllowAnonymous]
        public ActionResult Register()
        {
            return View();
        }

        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser
                {
                    Name = model.Name,
                    UserName = model.Email,
                    Email = model.Email,
                    PhoneNumber = String.Format("+{0}{1}", model.CountryCode, model.PhoneNumber),
                    CountryCode = model.CountryCode,
                };
                var result = await UserManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    // For more information on how to enable account confirmation and password reset 
                    // please visit http://go.microsoft.com/fwlink/?LinkID=320771
                    // Send an email with this link
                    // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
                    // var callbackUrl = Url.Action("ConfirmEmail", "Account", 
                    //                  new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
                    // await UserManager.SendEmailAsync(user.Id, "Confirm your account", 
                    //                  "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");


                    await UserManager.RequestPhoneNumberConfirmationTokenAsync(user.Id);

                    return RedirectToAction("VerifyRegistrationCode", new {message = ApplicationMessages.VerificationCodeSent});
                }
                AddErrors(result);
            }

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

        //
        // POST: /Account/LogOff
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
            return RedirectToAction("Index", "Home");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_userManager != null)
                {
                    _userManager.Dispose();
                    _userManager = null;
                }

                if (_signInManager != null)
                {
                    _signInManager.Dispose();
                    _signInManager = null;
                }
            }

            base.Dispose(disposing);
        }

        #region Helpers
        // Used for XSRF protection when adding external logins
        private const string XsrfKey = "XsrfId";

        private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }

        private void AddErrors(IdentityResult result)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
        }

        internal class ChallengeResult : HttpUnauthorizedResult
        {
            public ChallengeResult(string provider, string redirectUri)
                : this(provider, redirectUri, null)
            {
            }

            public ChallengeResult(string provider, string redirectUri, string userId)
            {
                LoginProvider = provider;
                RedirectUri = redirectUri;
                UserId = userId;
            }

            public string LoginProvider { get; set; }
            public string RedirectUri { get; set; }
            public string UserId { get; set; }

            public override void ExecuteResult(ControllerContext context)
            {
                var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
        }

        #endregion
    }
}
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AccountVerification.Web.Models;
using Authy.Net;
using Twilio;

namespace AccountVerification.Web
{
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            var twilio = new TwilioRestClient(TwilioSettings.AccountSID, TwilioSettings.AuthToken);

            var result = twilio.SendMessage(TwilioSettings.PhoneNumber,message.Destination, message.Body);

            Trace.TraceInformation(result.Status);

            // Twilio doesn't currently have an async API, so we return success.
            return Task.FromResult(0);
        }
    }

    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }

    public interface IApplicationUserManager
    {
        Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code);
        Task RequestPhoneNumberConfirmationTokenAsync(string userId);
    }

    // Configure the application user manager used in this application. UserManager is defined in 
    // ASP.NET Identity and is used by the application.
    public class ApplicationUserManager : UserManager<ApplicationUser>, IApplicationUserManager
    {
        private AuthyClient _authyApiClient;

        public ApplicationUserManager(IUserStore<ApplicationUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
            // Configure validation logic for usernames
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

            // Register two factor authentication providers. This application uses Phone and Emails 
            // as a step of receiving a code for verifying the user
            // You can write your own provider and plug it in here.

            manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "Your security code is: {0}"
            });
            manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "SecurityCode",
                BodyFormat = "Your security code is {0}"
            });
            manager.EmailService = new EmailService();
            manager.SmsService = new SmsService();

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }

        #region User Manager extended method interface
        public virtual async Task<IdentityResult> ConfirmPhoneNumberAsync(string userId, string code)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            IdentityResult identityResult;

            if (!AuthyApiClient.VerifyToken(user.AuthyUserId, code).Success)
            {
                identityResult = IdentityResult.Failed(ApplicationMessages.InvalidVerificationCode);
            }
            else
            {
                IUserPhoneNumberStore<ApplicationUser, string> store = (IUserPhoneNumberStore<ApplicationUser, string>)Store;

                await store.SetPhoneNumberConfirmedAsync(user, true);
                identityResult = await UpdateAsync(user);
            }
            return identityResult;
        }

        public virtual async Task RequestPhoneNumberConfirmationTokenAsync(string userId)
        {
            ApplicationUser user = await FindByIdAsync(userId);

            if (user == null)
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ApplicationMessages.UserIdNotFound));

            if (String.IsNullOrEmpty(user.AuthyUserId))
            {
                await RegisterAsAuthyUser(user);
            }

            AuthyApiClient.SendSms(user.AuthyUserId, force:true);
        }

        #region Private Methods
        private async Task RegisterAsAuthyUser(ApplicationUser user)
        {
            string authyUserId = AuthyApiClient.RegisterUser(user.Email, user.PhoneNumber.Replace(String.Format("+{0}", user.CountryCode), string.Empty), Convert.ToInt32(user.CountryCode)).UserId;
            user.AuthyUserId = authyUserId;

            await UpdateAsync(user);
        }

        private AuthyClient AuthyApiClient
        {
            get { return _authyApiClient ?? (_authyApiClient = new AuthyClient(AuthySettings.Key, test: false)); }
        }
        #endregion 

        #endregion
    }

    // Configure the application sign-in manager which is used in this application.
    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
}