Claim Based Security In ASP.NET Core In Just 8 Easy Steps

With Microsoft taking the OSI route, ASP.NET Web platform and ASP.NET project have undergone some significant changes. Also ASP.NET MVC Views and Controllers had minor transformation with the new platform skeleton. Let us go through the brief outline of the recent updates and how to implement claim based security in ASP.NET Core.

What is a Claim?

It is an attribute of identity that defines permissions. Permission means the step taken to perform the action and claim reside in the Microsoft.AspNetCore.Identity library. For successful dot net application development, we must look at the basic concepts for building a claim-based security and learn how to implement by creating an application on ASP.NET Core.

First we will create a “Hello World" ASP.NET Core web application using .NET core framework. To learn more about how to use Visual Studio and create ASP.NET Core applications, visit here:


In the above screenshot, the default template of Visual Studio .NET Web Project contains all the namespaces and assemblies required for our test project. Once the project is setup, we now need to focus on implementing a simple functionality -> Add a new Claim during the user registration/creational process and then apply the authorization restriction to the user with the Claim specified.

We need to use the below code in Startup.cs class which will help implement the security at application bootstrap, including security:

public class Startup
{
// Setting up the hosting environment
// and configuration
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true,
reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see
// http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method
// to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Add framework services.
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString
("DefaultConnection")));
// Add Identity stuff to the application services
services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient();
services.AddTransient();
}
// This method gets called by the runtime. Use this method to
// configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection
("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// 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
app.UseIdentity();
// Add external authentication middleware below. To configure
// them, please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/
{action=Index}/{id?}");
});
}
}

Next we will look at Models\ApplicationUser.cs class which contains an ApplicationUser class that derives from Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUser:

Add profile data for application users by
// adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
}

The above public class will be empty, where we need to add our claims. We will do it by applying code changes to demonstrate Claim-based security in real life using eight easy steps:

Step 1 - Enable Entity Framework Migrations – You need to enable “Entity Framework Migrations" if there are any iterative changes to Claims planned, as ASP.NET Identity uses Code First, auto-migration would be useful to perform database schema updates.

Step 2 - Add Relevant Properties - Now add all relevant properties to the ApplicationUser class (in file Models\ApplicationUser.cs) to store the Claims. We will take "BirthDate" and add this property to ApplicationUser. Do not forget to add the using System clause before class definition.

Step 3 - Add EF Migration – For updating database with the new added field. In the Package Manager Console, perform the following steps:
  • Add-Migration "Age" to create an upgrade script for our modification.
  • Update-Database to run a database schema update.

Now, we will implement the filling out of the Birthday value by adding a Birthday parameter to the User Registration form in the Models\AccountViewModels\RegisterViewModel.cs RegisterViewModel class using the below code:

public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be
at least {2} and at max {1} 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; }
}

Step 4 - Update the Views\Account\Register.cshtml File – Use the below code and update

...
class="col-md-2 control-label">
class="form-control" />
class="text-danger">
...

We also need to create a new account and test the new added field


Step 5 - Update the Controllers\AccountController.cs Register Method – We need to update the class method to pass the Birthday field in the above screenshot

// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task Register(RegisterViewModel model,
string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
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)
{
// For more information on how to enable account confirmation
// and password reset, please visit
// http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
// var code = await
// _userManager.GenerateEmailConfirmationTokenAsync(user);
// var callbackUrl = Url.Action("ConfirmEmail",
// "Account", new { userId = user.Id, code = code },
// protocol: HttpContext.Request.Scheme);
// await _emailSender.SendEmailAsync(model.Email,
// "Confirm your account",
// $"Please confirm your account by clicking this link:
// link");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account
with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}

Step 6 – Add the Claims - We need to create a mechanism for adding the Claims in ASP.NET Core with Microsoft.AspNetCore.Identity.SignInManager, by default. It includes only username and user identifier claims. SignInManager uses IUserClaimsPrincipalFactory to generate ClaimsPrincipal from TUser (in our case, from ApplicationUser).

We need to create our own implementation of IUserClaimsPrincipalFactory to add custom claims. We do not require the boilerplate code to be generated. We will simply derive it from the default UserClaimsPrincipalFactory which is already implementing IUserClaimsPrincipalFactory.

public class CustomClaimsPrincipalFactory :
UserClaimsPrincipalFactory
IdentityRole>
{
public CustomClaimsPrincipalFactory(
UserManager userManager,
RoleManager roleManager,
IOptions optionsAccessor) :
base(userManager, roleManager, optionsAccessor)
{
}
public async override Task
CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
// Add your claims here
((ClaimsIdentity)principal.Identity).
AddClaims(new[] {
new Claim(ClaimTypes.DateOfBirth,
user.BirthDate.ToString())
});
return principal;
}
}

Step 7 - Register CustomClaimsPrincipalFactory​

// This method gets called by the runtime. Use this method
// to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext(options =>
options.UseSqlServer(Configuration.
GetConnectionString("DefaultConnection")));
// Add Identity stuff to the application services.
services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
// Add Custom Claims processor
services.AddScoped,
CustomClaimsPrincipalFactory>();
services.AddMvc();
// Add application services.
services.AddTransient();
services.AddTransient();
}

Step 8 – Verify the Claim Security – Once the Claim setup is done with the above code, we now need to verify it. 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 the controllers' actions. The Claim “BirthDay" requires more checks, so we will implement verification of the Claim just for demonstration purposes in the Controllers\HomeController.cs About method using the below code:

public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
var user = HttpContext.User;
if (!user.HasClaim(c => c.Type ==
System.Security.Claims.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 == System.Security.Claims.ClaimTypes.DateOfBirth).Value);
if (calculateAge(dateOfBirth) >= minAge)
{
ViewBag.Message = "Your 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;
}

Any Claim can be extracted easily from the HttpContext.User at any point of the project. Now try to verify the code by registering one account with DOB (07/14/2016) and we will see the below result:


In the debug mode we will see the code like this:


Conclusion

This was one way to set up Claim-based security in ASP.NET Core with the help of ASP.NET Core Identity. Previously, it was very easy to add the Claims directly in the ApplicationUser implementation via overriding the GenerateUserIdentityAsync() method. But in ASP.NET Core, we need to implement IUserClaimsPrincipalFactory that is internally used by SignInManager. We also got more structured classes and interfaces implementation in ASP.NET Core, as logically SignInManager should indeed control sign-in processes (including claims) and ApplicationUser should be just an IdentityUser.

Let us know if you faced any difficulty in the comments section.