AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=2073&pId=-1
How to Implement 2-Step Verification in ASP.NET MVC
page
by Keyvan Nayyeri
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 70570/ 103

Introduction

As computer systems and particularly internet expand their role in our daily lives, security in computer systems becomes more important, and it’s a vital part of every programmer’s life to make his or her code secure. This is bolder when you go online on the internet or intranet and expose your application to more users and possible threats. The number of security attacks by hackers and their catastrophic impacts (in terms of privacy and money loss) has been increasing and we all can remember many of big highlights that happened in the past few months such as several security attacks to Sony PlayStation Network.

As an obvious consequence of these, security has been so important for me, just like everybody else, so I always spend a lot of time to reassure the security of my code and implement different mechanisms to prevent hackers from hurting my online identity.

As development technologies have evolved and online software have become bigger, newer techniques, methods, and tools are provided to improve the security of applications at different levels ranging from securing the programming languages to security mechanisms provided like those offered by IIS.

Having these said, a few weeks ago my blog was the target of a dictionary attack by a hacker for almost 24 hours. The hacker, who supposedly thought that I don’t have mechanisms for monitoring the accesses to my blog’s administration area, had tried to perform a dictionary attack on my login page to get access to my blog. This story ended up by me leaving a message for the hacker that was probably read later and forced him/her to stop this pointless attack, however, it made me extra-cautious about security so that I decided to take the security of my websites to the next level! In this article I’m going to explain the technique that I implemented, 2-step verification by phone, which is going to become a common technique in the near future. Searching on the internet, there wasn’t much information provided about this technique, especially for ASP.NET developers, so I hope that this article can contribute and encourage other developers to implement such mechanisms!

Background

Before moving on and talking about what I did, I have to say that in the past years there have been two common techniques used by developers to prevent dictionary attacks that are used either independently or in combination with each other:

·       CAPTCHA validation: Many websites implement some kind of CAPTCHA control that forces the user to enter the text displayed in an image if he fails to log into a website after a few attempts. This prevents automated dictionary attacks, but it has two big issues: first, it has a very bad impact on the usability of the site, and second, it is vulnerable to image processing techniques to parse the image and automatically detect the text in order to continue the dictionary attack.

·       Failed login attempt threshold: The other common mechanism is to store the number of failed login attempts and prevent the user to log into the site for a duration of time (e.g., 10 minutes) even if he provides the correct username and password combination. This method that I’ve used in some projects looks more promising than the CAPTCHA controls but has one weakness: it cannot prevent those types of attacks that are testing a combination of usernames and passwords together. In other words, the hacker may be able to find some valid usernames on the site that he can use for bad purposes later.

Although these two methods are very powerful in practice, they’re still vulnerable to different situations. If the user loses his password and somebody has the password, he can easily get access to the account. For some websites, this is not very critical, but for some sites and services this is a very dangerous situation. For example, a GMail user who has used his account for several years to store all his information and has put all his eggs in one basket may lose many things in his life by having his GMail account compromised!

And this is why Google implemented the 2-step verification mechanism on GMail accounts, and Microsoft also added a similar feature to the Live accounts. Previously, Facebook had a lighter form of this technique by informing a user via SMS whenever his or her account was accessed by somebody.

Essentially, this 2-step verification (that may be called with different names like two-step verification or phone verification as well) is one of different types of two-factor authentication (TFA) that tries to use text messaging in order to verify accesses to an account. This can be as simple as notifying a user that his account is accessed by a specific IP address at a specific time, or it can be more complex by requiring the user that he enters a code that is sent to his phone in order to confirm his identity when logging in.

Generally, the goal of 2-step verification is to use the cell phone number of a user to make sure that accesses to an account are legitimate, and the motivation behind this technique is that nowadays almost everybody is using a cell phone and he’s carrying that everywhere. Even if a password is compromised by some tricks, it is very hard to have access to somebody’s mobile phone unless it is physically stolen.

I believe that a 2-step verification is a vital part of some sites like banks, domain registrars, DNS managers, and site hosting services, especially the cloud-based solutions, because a chain is as weak as its weakest link, and if one part of the chain that makes a site is compromised, the whole site is compromised!

Implementation

As I mentioned above, there are different variants of implementing 2-step or phone verification on a website, and there isn’t a classic method that I can discuss here. The implementation also varies by the structure of a website, its requirements, and some other factors. However, in this post, I’m going to implement a simple prototype of a 2-step verification system for an ASP.NET MVC application that follows a standard approach. The implementation is easy to inspire and even simpler in ASP.NET Web Forms since you can use server controls to achieve many goals faster and easier.

Here I use the default ASP.NET MVC application template (with Razor view engine) provided by Microsoft and modify that so after the user provides the correct username and password combination, I send a text message with a verification code to a mobile number, and then ask the user to enter this code to be able to sign in. In order to save users from the hassle of redoing this verification every time, I use the IP address and user agent of the client as two factors to identify trusted machines. Of course, there are better ways to achieve uniqueness but I skip them here for the sake of simplicity that I need for educational purposes. After a successful login from a trusted machine/browser, user doesn’t need to redo this process, and only repeats that when he’s using a different IP address and/or browser.

SMS Communication

First things first! Looking at the problem defined above, an ASP.NET developer would guess that the implementation doesn’t have a very complex structure as long as the text messaging part is handled, and that is right. One of the biggest requirements of 2-step verification is a secure and reliable service to send your text messages from. This can be achieved by several services, but these days Twilio has become the de facto standard for phone communication for programmers. A cloud-based reliable solution with high security and simplified API wouldn’t leave doubts for you to use it as the solution unless you’re living outside the United States and Canada and may need to use your local service.

After referencing the Twilio API on Nuget (or manually), you can implement a very simple class to send a text message from your Twilio number to a destination number (Listing 1).

Listing 1: Text message communication

using Twilio;
 
namespace AspNetPhoneVerificationSample.Components
{
    public static class Messenger
    {
        public static void SendTextMessage(string number, string message)
        {
            TwilioRestClient twilioClient = 
                new TwilioRestClient("acccountSid""authToken");
            twilioClient.SendSmsMessage("+12065696562", number, message);
        }
    }
}
Verification Code Generation

The next step is to have a simple component that generates verification codes. Here, you have to note that having a short code may not be a good idea since it’s easy to generate automatically, and having a long code makes it hard for the end user to read and type it from his cell phone. I believe that a string between 6 to 8 characters would be fine. Besides, a string that consists of numbers can increase the chances of auto-generated attacks, and I’d prefer to use a combination of letters and numbers.

For this example, I use a simpler code verification component from what I would put on production. I had a blog post on writing a library for generating random passwords with different requirements in details before, and this code generation is exactly the same scenario, and that library can serve for this purpose as well.

Having this background, the code generator class looks like the code in Listing 2. It generates a random string of 8 characters long including 4 lower case letters and 4 numbers.

Listing 2: Random code generation

using System;
using System.Collections.Generic;
 
namespace AspNetPhoneVerificationSample.Components
{
    public static class CodeGenerator
    {
        public static string GenerateCode()
        {
            List<char> chars = new List<char>();
 
            chars.AddRange(GetLowerCaseChars(4));
            chars.AddRange(GetNumericChars(4));
 
            return GenerateCodeFromList(chars);
        }
 
        private static List<char> GetLowerCaseChars(int count)
        {
            List<char> result = new List<char>();
 
            Random random = new Random();
 
            for (int index = 0; index < count; index++)
            {
                result.Add(Char.ToLower(Convert.ToChar(random.Next(97, 122))));
            }
 
            return result;
        }
 
        private static List<char> GetNumericChars(int count)
        {
            List<char> result = new List<char>();
 
            Random random = new Random();
 
            for (int index = 0; index < count; index++)
            {
                result.Add(Convert.ToChar(random.Next(0, 9).ToString()));
            }
 
            return result;
        }
 
        private static string GenerateCodeFromList(List<char> chars)
        {
            string result = string.Empty;
 
            Random random = new Random();
 
            while (chars.Count > 0)
            {
                int randomIndex = random.Next(0, chars.Count);
                result += chars[randomIndex];
                chars.RemoveAt(randomIndex);
            }
 
            return result;
        }
    }
}
User Management

The next component is a user management class that allows us to validate the users, retrieve their verification codes from data store, and store the generated codes into the data store. For this example, I use a very simple XML storage and apply LINQ to XML to have access to my data, but a real world implementation should be much more sophisticated than this.

I assume that an ASP.NET developer is already familiar with LINQ to XML and finds the code in Listing 3 self-explanatory, so I don’t explain it in here.

Listing 3: User management

using System.Linq;
using System.Web;
using System.Xml.Linq;
 
namespace AspNetPhoneVerificationSample.Models
{
    public static class Users
    {
        public static bool ValidateUser(string username, string password)
        {
            // This is a simple single-user system
            if (username == "keyvan" && password == "pas$word")
                return true;
            return false;
        }
 
        public static string GetUserPhone(string username)
        {
            // This is a simple single-user system
            if (username == "keyvan")
                return "+1YOURPHONE”;
            return string.Empty;
        }
 
        public static string ReadValidationCode(string username)
        {
            string path = HttpContext.Current.Server.MapPath("~/App_Data/usercodes.xml");
 
            XDocument doc = XDocument.Load(path);
            string code = (from u in doc.Element("Users").Descendants("User")
                           where u.Attribute("name").Value == username
                           select u.Attribute("code").Value).SingleOrDefault();
            return code;
        }
 
        public static void StoreValidationCode(string username, string code)
        {
            string path = HttpContext.Current
                .Server.MapPath("~/App_Data/usercodes.xml");
 
            XDocument doc = XDocument.Load(path);
            XElement user = (from u in doc.Element("Users").Descendants("User")
                             where u.Attribute("name").Value == username
                             select u).SingleOrDefault();
 
            if (user != null)
            {
                user.Attribute("code").SetValue(code);
            }
            else
            {
                XElement newUser = new XElement("User");
                newUser.SetAttributeValue("name", username);
                newUser.SetAttributeValue("code", code);
                doc.Element("Users").Add(newUser);
            }
            doc.Save(path);
        }
    }
}

You also need to alter account models a little bit to have a new property for LogOnModel for verification code (Listing 4).

Listing 4: LogOn model

public class LogOnModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }
 
    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }
 
    [DataType(DataType.Password)]
    [Display(Name = "Verification code")]
    public string VerificationCode { get; set; }
 
    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
}
Trusted Clients

Not only we need to manage users and store/retrieve their verification codes, but also we need a mechanism to store trusted clients and retrieve them to/from data store. Again, I use a simple XML-based data store and write LINQ to XML code to store a trusted client for a particular username or retrieve an existing item (Listing 5).

Listing 5: Trusted clients

using System.Linq;
using System.Web;
using System.Xml.Linq;
 
namespace AspNetPhoneVerificationSample.Models
{
    public static class TrustedClients
    {
        public static bool ValidateClient(string username, string ip, 
            string useragent)
        {
            string path = HttpContext.Current.
                  Server.MapPath("~/App_Data/trustedclients.xml");
 
            XDocument doc = XDocument.Load(path);
            var client = (from c in doc.Element("Clients").Descendants("Client")
                           where
                           ((c.Attribute("username").Value == username) &&
                           (c.Attribute("ip").Value == ip) &&
                           (c.Attribute("useragent").Value == useragent))
                           select c).SingleOrDefault();
 
            if (client != null)
                return true;
            return false;
        }
 
        public static void AddClient(string username, string ip, string useragent)
        {
            string path = HttpContext.Current.
                Server.MapPath("~/App_Data/trustedclients.xml");
 
            XDocument doc = XDocument.Load(path);
            XElement newClient = new XElement("Client");
 
            newClient.SetAttributeValue("username", username);
            newClient.SetAttributeValue("ip", ip);
            newClient.SetAttributeValue("useragent", useragent);
 
            doc.Element("Clients").Add(newClient);
 
            doc.Save(path);
        }
    }
}
View

The next step is to add necessary user interface elements to views. Here I follow a simplistic approach to have a textbox on the LogOn view and disable or enable it whenever a flag in ViewBag (which is called VerificationCodeEnabled) is set (Listing 6).

Listing 6: LogOn view

@model AspNetPhoneVerificationSample.Models.LogOnModel
@{
    ViewBag.Title = "Log On";
}
<h2>
    Log On</h2>
<p>
    Please enter your user name and password. @Html.ActionLink("Register",
"Register")
    if you don't have an account.
</p>
<script src="@Url.Content%28" ~="" scripts="" jquery.validate.min.js")"="" 
type="text/javascript"></script>
<script src="@Url.Content%28" ~="" scripts="" 
jquery.validate.unobtrusive.min.js")"="" type="text/javascript"></script>
@Html.ValidationSummary(true, 
"Login was unsuccessful. Please correct the errors and try again.")
@using (Html.BeginForm())
{
    <div>
        <fieldset>
            <legend>Account Information</legend>
            <div class="editor-label">
                @Html.LabelFor(m => m.UserName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </div>
            <div class="editor-label">
                @Html.LabelFor(m => m.Password)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.Password)
                @Html.ValidationMessageFor(m => m.Password)
            </div>
            @if (ViewBag.VerificationCodeEnabled)
            {
                <div class="editor-label">
                    @Html.LabelFor(m => m.VerificationCode)
                </div>
                <div class="editor-field">
                    @Html.PasswordFor(m => m.VerificationCode)
                    @Html.ValidationMessageFor(m => m.VerificationCode)
                </div>
            }
            <div class="editor-label">
                @Html.CheckBoxFor(m => m.RememberMe)
                @Html.LabelFor(m => m.RememberMe)
            </div>
            <p>
                <input value="Log On" type="submit">
            </p>
        </fieldset>
    </div>
}
Controller

Having the prerequisites ready, all need to be done is to modify the existing LogOn action method in the AccountController to have verification feature enabled (Listing 7).

Listing 7: AccountController

//
// GET: /Account/LogOn
 
public ActionResult LogOn()
{
    ViewBag.VerificationCodeEnabled = false;
 
    return View();
}
 
//
// POST: /Account/LogOn
 
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    ViewBag.VerificationCodeEnabled = false;
 
    bool valid = false;
 
    if (ModelState.IsValid)
    {
        if (Users.ValidateUser(model.UserName, model.Password))
        {
            if (TrustedClients.ValidateClient(model.UserName,
 Request.UserHostAddress, Request.UserAgent))
            {
                valid = true;
            }
            else
            {
                if (string.IsNullOrEmpty(model.VerificationCode))
                {
                    ViewBag.VerificationCodeEnabled = true;
 
                    string code = CodeGenerator.GenerateCode();
                    Users.StoreValidationCode(model.UserName, code);
 
                    Messenger.SendTextMessage(Users.GetUserPhone(model.UserName),
 string.Format("Your verification code is {0}.", code));
 
                    View(model);
                }
                else
                {
                    if (Users.ReadValidationCode(model.UserName) == 
model.VerificationCode)
                    {
                        TrustedClients.AddClient(model.UserName, 
Request.UserHostAddress, Request.UserAgent);
                        valid = true;
                    }
                    else
                    {
                        ModelState.AddModelError("""The verification code is incorrect.");
                        ViewBag.VerificationCodeEnabled = true;
                    }
                }
            }
        }
        else
        {
            ModelState.AddModelError("""The user name or password provided is incorrect.");
        }
    }
 
    if (valid)
    {
        FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
        if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && 
returnUrl.StartsWith("/")
            && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("Index""Home");
        }
    }
 
    // If we got this far, something failed, redisplay form
    return View(model);
}

I assume that a programmer who is reading this text is able to read and understand the logic in this code, but in essence, the user credentials are checked and if they are correct/valid, the client’s properties (IP address and user agent) are checked to see if it is already trusted, otherwise, either the verification code is generated and sent to the user’s number or the code is retrieved and verified against the data store to log the user in.

Test

Testing this simple ASP.NET MVC application, you can enter the user credentials (Figure 1).

Figure 1: Log On form

If your information is correct, you’ll see a textbox appeared asking you for a verification code to enter. At the same time, code is generated and set to the mobile number of the user (Figure 2).

Figure 2: Text message received

You receive the number and can enter that to verify the user (Figure 3).

Figure 3: Enter verification code

And you’ll be signed into the site! Next time, if you visit the page with the same IP address and browser, you will automatically log in without dealing with the verification code.

Conclusion

As security becomes more important than ever, hackers are applying new techniques to attack websites and damage them with catastrophic impacts. A majority of such attacks focus on compromising user accounts to steal private/personal data or take the control of an account and use it for different purposes like stealing money. Since relying on a single password is not a very secure way to manage user accounts, having a 2-step verification system is necessary for many sites with critical information.

In this article I explained different steps to implement a simple 2-step verification system for user accounts in ASP.NET MVC using Twilio as the platform of SMS communication. Although this is a simple prototype, it’s easy to expand the idea to larger scales as well as ASP.NET Web Forms to build more secure sites on the internet.

Download

[Download Sample]

I’ve uploaded the source code sample for this article, so you can download and test it on your machine. Note that you need to set your own Twilio account information and user phone number in the code.



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-25 12:38:56 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search