Two Factor Authentication

ASP.NET Identity 2.0 Two Factor Authentication

knowventBanner

Since the release of the new ASP.NET Identity 2.0 there as been a lot of buzz around the support for two factor authentication. This adds a greater level of security around the ever insecure password authentication methods. If you are interested in the setup up ASP.NET Identity 2.0 to used cookie and token authentication check out my previous article.

This particular article will detail how to implement and setup two factor authentication using SMS as the second authentication method. Any SMS provider can be used to send authentication codes such as Twilio. This article will detail the process using Google’s Voice SMS service.

Setup ASP.NET Identity 2.0 Two Factor Authentication using SMS

For this article I have updated my MasterKey project project first used to demo the ASP.NET Identity 2.0 Cookie & Token Auth. The MasterKey Github project is a working example of the two factor authentication (just add your own SMS provider). This project allows users to pick whether they want to authenticate using just the password authentication (the UseCookieAuthentication config) or two factor (the UseTwoFactorSignInCookie config)

Configure UseTwoFactorSignInCookie (Startup.Auth.cs)

public void ConfigureAuth(IAppBuilder app)
{
    app.CreatePerOwinContext<AppUserIdentityDbContext>(AppUserIdentityDbContext.Create);
    app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);

    //This config is for password/single factor auth
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login")
    });
    //This sets up the app for Two factor auth
    app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
}

This snippet is where you will have the ASP.NET Identity Two Factor Authentication initial configuration applied. As detailed by the comments, the UseCookieAuthentication sets up the app to support single, password authentication and UseTwoFactorSignInCookie sets up the two factor authentication. The we configured UseTwoFactorSignInCookie to timeout the challenge after 5 minutes.


RegisterTwoFactorProvider (AppUserManager.cs)

public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
    var manager = new AppUserManager(new UserStore<AppUserIdentity>(context.Get<AppUserIdentityDbContext>()));
    manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<AppUserIdentity>
    {
        MessageFormat = "Your security code is: {0}"
    });
    manager.SmsService = new GoogleSmsService();
    return manager;
}

Since this app is configured to use two factor authentication we will want to register a provider that will define how to send the second factor authentication challenge. ASP.NET Identity 2.0 supports multiple two factor providers out of the box (the EmailTokenProvider and PhoneNumberTokenProvider). Here we elected to use the ‘PhoneNumberTokenProvider’ for SMS code challenges.

manager.SmsService = new GoogleSmsService();

Also notice my custom GoogleSmsService which implements the IIdentityMessageService interface. All this class is used to define how the SMS message will be sent to the phone. This is where you integrate with your choice of SMS provider.


GoogleSmsService

public class GoogleSmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your sms service here to send a text message.
        //You need to make sure your google account is signed up 
        SharpVoice.Voice v = new SharpVoice.Voice("YourAccount@gmail.com", "YourPassword");
        v.SendSMS(message.Destination, message.Body);
        return Task.FromResult(0);
    }
}

Here I am using my Google voice account to transmit the authentication code. I am using SharpVoice wrapper library to simplify my calls to Google’s Voice API. If you have a preferred SMS provider simply call it here. The message.Destination is the users phone number and the message.Body is the code message.

NOTE: If you plan on using your Google account for sending SMS then be sure Google Voice is setup on your account to work with Google Voice’s API.


Allow Users to Configure and Use ASP.NET Identity 2.0 Two Factor Authentication

Now the the application is setup to support two factor authentication you will need to implement a UI for your users. Here we will detail how to allow users to enable/disable two factor authentication and add/verify a phone number

In the MasterKey example project there is a working implementation of this functionality in the Manage.cshtml view/controller. Here we will detail the Controller action snippets that support the functionality.

Enable Two Factor Authentication

public async Task<ActionResult> EnableTFA()
{
    await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true);
    var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
    }
    return RedirectToAction("Manage", "Account");
}

The SetTwoFactorEnabledAsync method is called to enable this users identity configuration for two factor authentication. The next step just signs the user back in to update the user’s identity context.


Disable Two Factor Authentication

public async Task<ActionResult> DisableTFA()
{
    await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false);
    var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
    }
    return RedirectToAction("Manage", "Account");
}

Same thing as above. The SetTwoFactorEnabledAsync method is called to disable this users identity configuration for two factor authentication.


Adding and Validating a SMS Phone Number

This process involves a workflow of adding a phone number, validating it and persisting it for usage when authenticating.

Adding a phone number and validating it requires sending an initial token via SMS.

// Send result of: UserManager.GetPhoneNumberCodeAsync(User.Identity.GetUserId(), phoneNumber);
// Generate the token and send it
var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), model.Number);
if (UserManager.SmsService != null)
{
    var message = new IdentityMessage
    {
        Destination = model.Number,
        Body = "Your security code is: " + code
    };
    await UserManager.SmsService.SendAsync(message);
}
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });

Calling GenerateChangePhoneNumberTokenAsync will register a code with the phone number the user has entered. The UserManager.SmsService.SendAsync method will send the code to the phone using the GoogleSmsService provider we configured earlier.


var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
if (result.Succeeded)
{
    var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
    }
    return RedirectToAction("Manage");
}

Once the user submits the validation token they received via SMS the ChangePhoneNumberAsync method is called to verify the code is correct. After the code if verify the phone number verification process is complete.


The ASP.NET Identity Two Factor Authentication Login Process

Again, this is another user workflow process. When the user first logs in we will need to determine if the user has enabled two factor authentication.

// To enable password failures to trigger lockout, change to shouldLockout: true
var result = await SignInHelper.PasswordSignIn(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
    case MasterKey.Identity.SignInHelper.SignInStatus.Success:
        return RedirectToLocal(returnUrl);
    case MasterKey.Identity.SignInHelper.SignInStatus.LockedOut:
        return View("Lockout");
    case MasterKey.Identity.SignInHelper.SignInStatus.RequiresTwoFactorAuthentication:
        return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
    case MasterKey.Identity.SignInHelper.SignInStatus.Failure:
    default:
        ModelState.AddModelError("", "Invalid login attempt.");
        return View(model);
}

The PasswordSignIn helper method will return either a Success result or RequiresTwoFactorAuthentication if further action is required to login.

Note: The SignInHelper class was included in the ASP.NET Identity 2.0 sample projects. As noted by the ASP.NET Identity team, “These help with sign and two factor (will possibly be moved into identity framework itself)”.


Once it is determined that authentication process requires two factor the logic redirects the workflow to the SendCode controller action.

public async Task<ActionResult> SendCode(string returnUrl)
{
    var userId = await SignInHelper.GetVerifiedUserIdAsync();
    if (userId == null)
    {
        return View("Error");
    }
    var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
    var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
    return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl });
}

This step could be optional if your application only allows a single two factor provider. However, here are calling GetValidTwoFactorProvidersAsync to get all of the providers registered for this user. This will be returned to the view to be displayed as options the user will use for the second factor.


After the user selects the provider to use the next step calls SendTwoFactorCode helper method.

SignInHelper.SendTwoFactorCode(model.SelectedProvider)

Which in turn calls the identity core methods

var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);
await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);

At this point the user will be receiving a code on their phone to be used to authenticate. Once the user enters the pass code the following snippet performs authenticity verification.

var result = await SignInHelper.TwoFactorSignIn(model.Provider, model.Code, isPersistent: false, rememberBrowser: model.RememberBrowser);
switch (result)
{
    case MasterKey.Identity.SignInHelper.SignInStatus.Success:
        return RedirectToLocal(model.ReturnUrl);
    case MasterKey.Identity.SignInHelper.SignInStatus.LockedOut:
        return View("Lockout");
    case MasterKey.Identity.SignInHelper.SignInStatus.Failure:
    default:
        ModelState.AddModelError("", "Invalid code.");
        return View(model);
}

The TwoFactorSignIn helper method performs the following using the identity core methods.

if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
{
    // When token is verified correctly, clear the access failed count used for lockout
    await UserManager.ResetAccessFailedCountAsync(user.Id);
    await SignInAsync(user, isPersistent, rememberBrowser);
    return SignInStatus.Success;
}

VerifyTwoFactorTokenAsync validates the authenticity of the code sent to the users phone. If it returns true then the authentication is valid and complete. The user is signed in and redirected to the home screen.


That is it! The ASP.NET Identity 2.0 two factor authentication offers a lot of great functionality right out of the box. If you choose to deliver your second factor authentication codes using another transport then creating a new provider is simple and straight forward.

Again, the sample code for this article can be found on Github: MasterKey Authenication or download the ZIP

Let me know what you think or if you have any questions.

Posted in .NET, MVC.
  • Larserik Elander

    Hi, nice article!
    I’m developing a tokenbased bearer authentication. I would like to add two-factor authentication to it, but cannot find the solution. Do you have any information to share?

    • Justin Hyland

      I would recommend reading through one of my previous articles: http://blog.iteedee.com/2014/03/asp-net-identity-2-0-cookie-token-authentication/

      This will not address your question directly. However, using the token authentication method instead of the cookie authentication shown in this article might be all you need.

    • Larserik Elander

      I have read that one and many more. Implementing an authentication with bearer token together with the web api is easy to find examples on the web.
      But adding two-factor authentication seems harder. Haven’t find anyone yet that has discribed it. You might be the first one 🙂

    • Ali Mohammad Fawzi

      Hi Larserik, did you find any solution for 2FA with bearer token?

    • Justin Hyland

      No, @alimohammadfawzi:disqus I have not. I guess I really need to understand how this would be applied to your application and I could provide a solution.

      The bear token model assume you will be doing system 2 system authorization such as API authentications and two factor (in this case) requires a human. I guess you might be trying to use a hammer to when you might need a different tool.

      You could issue a bearer token after the user logged in via web form and passed the two factor auth. Then you could issue a bearer token under that context/session of the brower executing this code below. But this would just be redudent since you could just use the browser/cookie session.

      var userIdentity = UserManager.FindAsync(user, password).Result;
      if (userIdentity != null)
      {
      var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
      identity.AddClaim(new Claim(ClaimTypes.Name, user));
      identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userIdentity.Id));
      AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
      var currentUtc = new SystemClock().UtcNow;
      ticket.Properties.IssuedUtc = currentUtc;
      ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
      string AccessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
      return AccessToken;
      }

  • Justin Hyland

    I would recommend reading through one of my previous articles:http://blog.iteedee.com/2014/03/asp-net-identity-2-0-cookie-token-authentication/

    This will not address your question directly. However, using the token authentication method instead of the cookie authentication shown in this article might be all you need to do.

  • Peter

    In the SignInHelper.TwoFactorSignIn why is isPersistent = false? I’ve found that with this set, the .AspNet.TwoFactorRememberBrowser cookie get sets to expire on session exit, and if I set it to true, both the .AspNet.TwoFactorRememberBrowser cookie and the .AspNet.ApplicationCookie cookie get set to expire in 30days. But I don’t want a persistent login, just a persistent Two factor sign in

    • Justin Hyland

      So I am guessing you are wanting to use Two Factor auth the first time or for it just to persist that two factor was used in the past but still require the user to user their username and password each time? I think the identity framework views two factor as a single login workflow. It is either on or off at the app level.

      I haven’t try this but perhaps you can create a custom CookieAuthenticationProvider and configure the OnValidateIdentity function call to include your required cookie validation. It is difficult to say that you could conditional enable two factor based on user rather than the application.

  • Aaron Forman

    If I have multiple users logging into an application through Google Chrome. AND each of them receives a code, enters the code, and clicks remember me on the verification page…. on each iteration of that cycle the TwoFactorRememberBrowser cookie is removed and replaced with a new one. I assume based on the users securitystamp. So when the first person logs back in they have to go through 2fa all over again…
    is there any way to get the 2fa remember browser option to be stored by user?
    thanks

    • Justin Hyland

      This is a really good question. I could definitely see how constantly re-authing using 2fa on the same machine would drive people crazy.

      Just to clarify. You want to “Trust” that users account with that browser/machine. The first time they perform the 2fa then they will not have to do for future logins.

      Assuming that is correct you could supply the browser with an encrypted cookie after first successfully login using 2fa.

      Each subsequent logins you could check if that user has performed the 2fa in the past and circumvent the 2fa check at this location:

      https://github.com/hylander0/MasterKeyAuthentication/blob/acdb62550c3c905e0b3eefce71ee22e0bba12d67/MasterKey/Identity/SignInHelper.cs#L136

      For the warranty, I have never done it so take this with a grain of salt. The 2fa process is linear so i don’t see a issue the the current implementation that would not allow you to programmatically short circuit the 2fa.

    • Aaron Forman

      Ok… so Justin… this is working by design ?

      Curious because they have this call: TwoFactorBrowserRememberedAsync

      Which checks per user if they have 2fa’d for a browser… weird huh? seems like a bug doesn’t it?
      aaron

    • Justin Hyland

      Great point. I have never tried using CreateTwoFactorRememberBrowserIdentity across multiple logins on the same browser. I am *assuming* that this cookie is a one user to one cookie relationship only to get overwritten by the next user to login. From what you are seeing I would agree that this is a bug. However, the team supporting the library would say it is working by design.

      Perhaps they want it to work inline with how a single form auth works and only have one remembered.