ASP.NET MVC and Claim-Based Security

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

By Taras Kholopkin.

What Is a Claim and How Is It Related to Security Permission?

Nowadays, building claim-based security in Web applications is pretty much a trend. But, not everybody understands what a claim is and why it should be used in applications. Of course, it is a matter of architecture and requirements to define an applicable security mechanism—role-based access or claim-based security—or even no authentication or no authorization mechanism at all.

By its definition, a claim is an attribute used to describe the subject (in our case, identity). Claim-based identity is a common way for applications to acquire needed identity information about users inside their organization, in other organizations, and on the Internet.

Claim is a wider term as opposed to permission in a context of authorization. For example, the user's 'Age' is a claim where 'WatchScaryMovie' is a permission. Based on the claim, it is possible to identify the permission; for example, "All people with 'Age' more than 16 can do <something>." But, it would be not easy to identify the identity's attribute such as 'Age' when "All people who can watch scary movie are ... years old". So, a claim is an attribute of identity that can define the permissions, whereas permissions are just rights to do something.

ASP.NET and Claim-Based Security

Since Windows Identity Foundation (WIF) got integrated into the .NET 4.5 Framework, it is quite easy to apply claim-based security to building Web applications in the .NET Web World. The vast majority of stuff for building claim-based security is located in the System.Security.Claims and Microsoft.AspNet.Identity namespaces. These are the namespaces I will use as an example in this article.

ASP.NET MVC Example with Claim-Based Security

Create a simple "Hello World" ASP.NET MVC Web application in your Visual Studio. If you need help with this, please refer to the steps in my previous article. In fact, the main job is done. The default Visual Studio .NET Web Project template has already added all the namespaces and assemblies required for our test project. The only thing left is to implement simple functionality to add a new claim during the user registration/creational process and then apply the authorization restriction to the user with the claim specified.

Let's quickly emphasize the most important pieces of functionality responsible for security work:

App_Start/Startup.Auth.cs—Class for security bootstrapping

public void ConfigureAuth(IAppBuilder app)
   {
      // Configure the db context, user manager, and signin
      // manager to use a single instance per request
      app.CreatePerOwinContext(ApplicationDbContext.Create);
      app.CreatePerOwinContext<ApplicationUserManager>
         (ApplicationUserManager.Create);
      app.CreatePerOwinContext<ApplicationSignInManager>
         (ApplicationSignInManager.Create);

      // Enable the application to use a cookie to store
      // information for the signed-in user and to use
      // a cookie to temporarily store information about
      // a user logging in with a third-party login provider
      // Configure the sign-in cookie
      app.UseCookieAuthentication(new CookieAuthenticationOptions
      {
         AuthenticationType = DefaultAuthenticationTypes.
            ApplicationCookie,
         LoginPath = new PathString("/Account/Login"),
         Provider = new CookieAuthenticationProvider
         {
            // Enables the application to validate the security
            // stamp when the user logs in.
            // This is a security feature that is used when you
            // change a password or add an external login to
            // your account.
            OnValidateIdentity = SecurityStampValidator.
               OnValidateIdentity<ApplicationUserManager,
               ApplicationUser>(
               validateInterval: TimeSpan.FromMinutes(30),
               regenerateIdentity: (manager, user) =>
                 user.GenerateUserIdentityAsync(manager))
            }
      });
      app.UseExternalSignInCookie(DefaultAuthenticationTypes.
                                  ExternalCookie);

      // Enables the application to temporarily store user
      // information when they are verifying the second factor
      // in the two-factor authentication process.
      app.UseTwoFactorSignInCookie
         (DefaultAuthenticationTypes.TwoFactorCookie,
          TimeSpan.FromMinutes(5));

      // Enables the application to remember the second login
      // verification factor, such as phone or email.
      // Once you check this option, your second step of
      // verification during the login process will be
      // remembered on the device from where you logged in.
      // This is similar to the RememberMe option when you
      // log in.
      app.UseTwoFactorRememberBrowserCookie
         (DefaultAuthenticationTypes.
          TwoFactorRememberBrowserCookie);
   }

App_Start/IdentityConfig.cs—Configuration and extension of ASP.NET Identity. Class ApplicationUserManager is clearly described here.

Models/IdentityModels.cs—Contains ApplicationUser class:

public class ApplicationUser : IdentityUser
   {
      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;
      }
   }

This is the exact place to add some claims to the application user. So, let's start making code changes to demonstrate claim-based security in real life:

1. Enable Entity Framework Migrations

Enable Entity Framework Migrations if there any iterative changes to claims planned. Because ASP.NET Identity uses Code First, auto-migration would be useful to perform database schema updates. More about Code First Migrations may be found here:

Visual Studio main menu -> Tools -> NuGet Package Manager -> Package Manager Console -> Type in "Enable-Migrations" and press Enter.

2. Add Relevant Properties

Add all relevant properties to the ApplicationUser class (in file Models/IdentityModels.cs) to store the claims. As we reviewed the example with Age, let's take 'BirthDate' and add the property to ApplicationUser. Don't forget to add using System before the class definition.

3. Add EF Migration

Add EF migration to the update database with a new field. In the Package Manager Console, perform the following steps:

  1. Add-Migration "'Age' <press enter> to create an upgrade script for our modification
  2. Update-Database <press enter> to run a database schema update

4. Add the Birthday Value

Now, we need to implement the filling out of the Birthday value. To make it more obvious, add the Birthday parameter to the User Registration form in the Models\AccountViewModels.cs RegisterViewModel class:

public class RegisterViewModel
   {
      [Required]
      [EmailAddress]
      [Display(Name = "Email")]
      public string Email { get; set; }

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

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

      [Required]
      [Display(Name ="Date of Birth")]
      [DataType(DataType.Date)]
      public DateTime BirthDate { get; set; }
   }

Add using System before the class definition.

5. Update the Views\Account\Register.cshtml File

Update the Views\Account\Register.cshtml file with a new field:

...
   <div class="form-group">
      @Html.LabelFor(m => m.BirthDate, new
         { @class = "col-md-2 control-label" })
      <div class="col-md-10">
         @Html.TextBoxFor(m => m.BirthDate, new
            { @class = "form-control" })
      </div>
   </div>
...

Secure1
Figure 1: Registering a new account

6. Update the Controllers\AccountController.cs Register Method

Update the Controllers\AccountController.cs Register method to pass Birthday:

   // POST: /Account/Register
   [HttpPost]
   [AllowAnonymous]
   [ValidateAntiForgeryToken]
   public async Task<ActionResult> Register(RegisterViewModel model)
   {
      if (ModelState.IsValid)
      {
         var user = new ApplicationUser { UserName = model.Email,
            Email = model.Email, BirthDate = model.BirthDate };
         var result = await UserManager.CreateAsync(user,
             model.Password);
         if (result.Succeeded)
         {
            await SignInManager.SignInAsync(user, isPersistent:false,
               rememberBrowser:false);

            // 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>");

            return RedirectToAction("Index", "Home");
         }
         AddErrors(result);
      }

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

7. Update ApplicationUser Class

Update the ApplicationUser class by setting the DOB Claim:

public class ApplicationUser : IdentityUser
   {
      public DateTime BirthDate { 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
         userIdentity.AddClaim(new Claim(ClaimTypes.DateOfBirth,
            this.BirthDate.ToString()));

         return userIdentity;
      }
   }

Now, we implemented a Claim setup during the user's registration.

8. Verify the Claim

The only thing left is to verify the Claim. It is a common practice to write custom Authorize filters to verify the availability and particular value of the Claim pair, and then put that filter on controllers' actions, and so forth. For example:

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
   {
      private string claimType;
      private string claimValue;
      public ClaimsAuthorizeAttribute(string claimType,
         string claimValue)
      {
         this.claimType = claimType;
         this.claimValue = claimValue;
      }
      public override void OnAuthorization(AuthorizationContext
         filterContext)
      {
         var user = filterContext.HttpContext.User as
            ClaimsPrincipal;
         if (user.HasClaim(claimType, claimValue))
         {
            base.OnAuthorization(filterContext);
         }
         else
         {
            base.HandleUnauthorizedRequest(filterContext);
         }
      }
   }

Claim Birthday requires more checks, so we implement verification of the claim just for demonstration purposes in the Controllers\HomeController.cs About method:

   public class HomeController : Controller
   {
      public ActionResult Index()
      {
         return View();
      }

      public ActionResult About()
      {
         var user = HttpContext.User as ClaimsPrincipal;
         if (!user.HasClaim(c => c.Type ==
            ClaimTypes.DateOfBirth))
         {
            ViewBag.Message = "Cannot detect the Age -
               Claim is absent.";
            return View();
         }

         int minAge = 16;
         var dateOfBirth = Convert.ToDateTime(user.FindFirst(c =>
            c.Type == ClaimTypes.DateOfBirth).Value);

         if (calculateAge(dateOfBirth) >= minAge)
         {
            ViewBag.Message = "You can view this page.";
         }
         else
         {
            ViewBag.Message = "Your cannot view this page -
               your age is bellow permitted one.";
         }

         return View();
      }

      private int calculateAge(DateTime dateOfBirth)
      {
         int calculatedAge = DateTime.Today.Year -
            dateOfBirth.Year;
         if (dateOfBirth >
            DateTime.Today.AddYears(-calculatedAge))
         {
            calculatedAge--;
         }
         return calculatedAge;
      }

      public ActionResult Contact()
      {
         ViewBag.Message = "Your contact page.";

         return View();
      }
   }

As you see, the claims may be easily extracted of the User at any project part.

So, let's try to verify that the code works. I registered one account with DOB 04/14/2016. Here is the result:

Secure2
Figure 2: Verifying the code

Current security implementation in the Web application generated some other claims (in debug window):

Secure3
Figure 3: Claims being generated

That was a step-by-step guideline to set up claim-based security with the help of ASP.NET Identity.

About the Author

Taras Kholopkin is a senior solutions architect at SoftServe. He is also a frequent contributor to the SoftServe blog. With more than 10 years in the industry, he has extensive experience within the U.S. IT market. He is responsible for software architectural design and development of enterprise and SaaS solutions (including healthcare solutions).



Related Articles

Comments

  • another solution

    Posted by khaled on 08/18/2017 07:33am

    create AgeAuthorize derived from AuthorizeAttribute class that check age and decorate the protected action with [AgeAuthorize("16")]

    Reply
  • Claims in a Method?

    Posted by MW on 05/17/2017 01:10pm

    Great article! Is it possible to view the claims in a method that can be accessed throughout the site? For example: public string GetUserID(){ return user.FindFirst(c = c.Type == ClaimTypes.NameIdentifier)?.Value.toString(); } So, whenever you need the UserID, you can simply call the GetUserID method. I've tried this an put it into a controller, but I continue to get NullException errors.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date