Moot Points

Chronicles of a Software Engineer.

  Home  |   Contact  |   Syndication    |   Login
  3 Posts | 0 Stories | 3 Comments | 0 Trackbacks

News

Archives

Post Categories

If you use Gmail, you may be familiar with Google’s 2-step verification process. It’s an extra verification step using your cell phone. If you turn on the feature, once you enter your username and password, you will be sent a verification code via text message, voice, or Google Authenticator. This is a great way to add an extra layer of security because a malicious user would need your phone as well as your login information.

Now that we’ve established that 2-Step authentication is a good idea, how can we do that in our own applications? “It must be extremely difficult!”

Stop being such a pessimist, its actually pretty easy.

In my last post, I showed GVoiceSharp (a Google Voice API for .Net), lets use that. If you would rather use an officially supported solution since Google does not support an API for Voice, you could go with a paid SMS provider like Twillio, but I prefer free.

Since it’s the easiest to demonstrate, I’m using forms authentication with Web.config credential storage. In the HomeController I’ve added the Authorize attribute to make sure those requests will be routed to the login page, you’ll want to do this on any page that requires authorization.

   1:  [Authorize]
   2:  public ActionResult Index()

Lets look at the AccountController; in the HttpGet version of the Index action, we need to get the return Url provided by forms authentication. We’ll need to carry this forward through the requests so we can return the user to the proper place after being authenticated. I’m storing it in the ViewBag and passing it forward into the form post.

Controller:

   1:  [AllowAnonymous]
   2:  public ActionResult Index(string returnUrl)
   3:  {
   4:      ViewBag.ReturnUrl = returnUrl;
   5:      return View();
   6:  }

View:

   1:  @using (Html.BeginForm(new { ViewBag.ReturnUrl }))

 

The heavy lifting is done in the HttpPost version of the Index action.

   1:  [HttpPost, AllowAnonymous]
   2:  public ActionResult Index(LoginModel model, string returnUrl)
   3:  {
   4:      if (ModelState.IsValid)
   5:      {
   6:          if (FormsAuthentication.Authenticate(model.UserName, model.Password))
   7:          {
   8:              var token = new Random().Next(100000, 1000000);
   9:   
  10:              TempData["UserName"] = model.UserName;
  11:              TempData["TwoStepToken"] = token.ToString();
  12:   
  13:              using (var conn = GVoiceConnectionFactory.Create())
  14:              {
  15:                  conn.Login(ConfigurationManager.AppSettings["GVoiceUser"], ConfigurationManager.AppSettings["GVoicePassword"]);
  16:                  conn.SendSms(ConfigurationManager.AppSettings["SMSNumber"], "Auth Token: " + token);
  17:              }
  18:              return RedirectToAction("SecondAuth", new { ReturnUrl = returnUrl });
  19:          }
  20:          ModelState.AddModelError("", "The user name or password provided is incorrect.");
  21:      }
  22:      ViewBag.ReturnUrl = returnUrl;
  23:      return View(model);
  24:  }

Once the user puts in the correct username and password, we generate a 6-digit authentication token, and send it via SMS using GVoiceSharp. In a real world application we would want to abstract away the SMS provider, making it easier to test, and even change providers. Notice we’re storing the username along with the token, we’ll need it to set the authorization cookie in the next step of the process. I find that storing the authorization token in TempData works best since it persists between requests until its read, then gets cleared out. Another thing to note; I’m storing the login information in the config file, including the outgoing phone number. In a real world scenario, we would want an authentication phone number associated with each individual user.

For the second authentications step, I created a simple model with one property, and some metadata for display and validation purposes.

   1:  public class SecondAuthModel
   2:  {
   3:      [Required]
   4:      [Display(Name = "Authorization Token")]
   5:      public string Token { get; set; }
   6:  }

In the SecondAuth HttpPost controller action, we’re going to accept a SecondAuthModel, along with the return url(again), and check the token against what we have stored in temp data. If everything checks out, we’re going to set the authentication cookie for the specified user, and redirect to the returnUrl.

   1:  [HttpPost, AllowAnonymous]
   2:  public ActionResult SecondAuth(SecondAuthModel model, string returnUrl)
   3:  {
   4:      if (ModelState.IsValid)
   5:      {
   6:          if (TempData["TwoStepToken"] as string == model.Token)
   7:          {
   8:              FormsAuthentication.SetAuthCookie(TempData["UserName"] as string, false);
   9:   
  10:              if (Url.IsLocalUrl(returnUrl))
  11:                  return Redirect(returnUrl);
  12:                      
  13:              return RedirectToAction("Index", "Home");
  14:          }
  15:          return Redirect("/Account/?returnUrl=" + returnUrl);
  16:      }
  17:      ViewBag.ReturnUrl = returnUrl;
  18:   
  19:      return View();
  20:  }

And that’s it, not too hard was it? With just a few extra steps, we added an additional layer of security to our application.

The complete demo application (Mvc2StepAuthenticatio) is available on GitHub. Keep in mind, to run the application, you’ll need to put in your own Google Voice credentials in the appSettings section of the web.config.

posted on Sunday, December 30, 2012 2:29 AM