ASP.NET Core Authentication Using ASP.NET Core Identity

Jignesh Trivedi   Source Code  Print 
07 Sep 2018
13 Sep 2018
Advanced
351

Authentication is process of checking the identity of the user that accessing our application. Most of the application has login feature and application validate user identity against any trusted source such as database or external login providers (i.e. facebook, gmail, twitter etc.). This process is called Authentication. Authorization is process of validating privileges to access a resource of the application. After successful login to the application, authorization mechanism checks whether login user has privileges to access the application resource.

ASP.Net Core provides identity membership system that enable us to add login functionality to our application. Identity can be added by creating user account or can be use external login provider such as facebook, twitter. To configure the Identity in our application we can either use SQL server database to stored user information or use another persistent store such as Azure Table Storage. ASP.net Core project template allow us to create application using .net Core Identity. the application can be created by using Visual studio or Command line tool.

Using following command, we can create application using Command Line Tool in asp.net core.

 > donet new WebApplication1 --auth Individual
 

The template adds following code to ConfigureServices method of the startup class. This code is to add the identity service to our application. In this code, I am using "services.AddDefaultIdentity" method that adds the default identity system configuration for specific user.

 public void ConfigureServices(IServiceCollection services)
 {
 …
 …
 services.AddDefaultIdentity<IdentityUser>()
 .AddEntityFrameworkStores<ApplicationDbContext>();
 ….
 }
 

Alternatively, we can use “AddIdentity” method that adds the identity configuration for specific role and user. The AddIdentity method use to Role base authentication. By calling "app.UseAuthentication" method (that adds authentication middleware to the request pipeline) in Configure method of startup class, we can make identity available to the application.

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
 {
 …
 …
 app.UseAuthentication();
 …
 }
 

The ASP.net Core template adds default database connection string in appsetting.json file. The template adds database name as format "aspnet-{project name}-{guid}". This connection string is used in initialization of DBContext service in ConfigureService method. Here we need to replace this with actual connection string.

Appsettings.json

 
 {
 "ConnectionStrings": {
 "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=AspnetIdentity;Trusted_Connection=True;MultipleActiveResultSets=true"
 }
 }
 

Now all the configuration related to authentication using Identity is done. When I launch the application and click on "Register" link, system will asking for database migration if database is not compatible with application or database does not exist.

We can do the database migration either by clicking "Apply Migrations" button or by using command line (using command: "dotnet ef database update").The database migration will generate Identity membership related tables.

This UI for login and registration are defined in "Microsoft.AspNetCore.Identity" library as a Razor Pages. We can view or download source code for these pages from GitHubhttps://github.com/aspnet/Identity/tree/master/src/UI/Areas. We can also do the customization in login and registration razor page by defining our own pages. In this library both are defined under “Identity” area. So, if you don’t want to change navigation, you need to add razor pages under “Area/Identity/Pages/Account” folder. In this example, I have added my custom login page. I have added “Account” controller and I have three methods: Login, Logout and AccessDenied with in this controller.

AccountController.cs

 using System.ComponentModel.DataAnnotations;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Identity;
 using Microsoft.AspNetCore.Mvc;
 namespace WebApplication1.Controllers
 {
 [AllowAnonymous]
 public class AccountController : Controller
 {
 private readonly SignInManager<IdentityUser> signInManager;
 
 public AccountController(SignInManager<IdentityUser> signInManager)
 {
 this.signInManager = signInManager;
 }
 
 [HttpGet]
 public IActionResult Login()
 {
 return View();
 }
 
 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> Login(LoginModel model, string returnUrl = null)
 {
 ViewData["ReturnUrl"] = returnUrl;
 if (ModelState.IsValid)
 {
 var result = await signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
 if (result.Succeeded)
 {
 return RedirectToAction(nameof(HomeController.Index), "Home");
 }
 else
 {
 ModelState.AddModelError(string.Empty, "Invalid login attempt.");
 return View(model);
 }
 }
 return View(model);
 }
 
 [HttpPost]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> Logout()
 {
 await signInManager.SignOutAsync();
 return RedirectToAction("Login");
 }
 
 public IActionResult AccessDenied()
 {
 return View();
 }
 }
 public class LoginModel
 {
 [Required]
 public string UserName { get; set; }
 [Required]
 [DataType(DataType.Password)]
 public string Password { get; set; }
 [Display(Name = "Remember me?")]
 public bool RememberMe { get; set; }
 }
 }
 

Login.cshtml

 @model WebApplication1.Controllers.LoginModel
 <div class="form-group">
 <label asp-for="UserName" class="col-md-2 control-label"></label>
 <div class="col-md-10">
 <input asp-for="UserName" class="form-control" />
 <span asp-validation-for="UserName" class="text-danger"></span>
 </div>
 </div>
 <div class="form-group">
 <label asp-for="Password" class="col-md-2 control-label"></label>
 <div class="col-md-10">
 <input asp-for="Password" class="form-control" />
 <span asp-validation-for="Password" class="text-danger"></span>
 </div>
 </div>
 <div class="form-group">
 <div class="col-md-offset-2 col-md-10">
 <div class="checkbox">
 <label asp-for="RememberMe">
 <input asp-for="RememberMe" />
 @Html.DisplayNameFor(m => m.RememberMe)
 </label>
 </div>
 </div>
 </div>
 <div class="form-group">
 <div class="col-md-offset-2 col-md-10">
 <button type="submit" class="btn btn-primary">Log in</button>
 </div>
 </div>
 </form>
 </section>
 </div>
 </div>
 </div>
 <div class="col-md-3"></div>
 </div>
 

Configure Identity

Identity comes with some default configuration such as password policy, lockout policy, etc. We can also override this default configuration based on our requirement. We can override following options

PasswordOptions (Password Policy)

Using PasswordOptions, we can put some restrictions to set password. Followings are passwords options supported by identity service.

  • RequireDigit : it specify that password must contains at least one digit

  • RequiredLength : it specify that minimum length of password

  • RequireNonAlphanumeric : it specify that user must enter a non-alphanumeric character in the password

  • RequireUppercase : it specify that password must contains at least one upper case character

  • RequireLowercase : it specify that password must contains at least one lower case character

  • RequiredUniqueChars : it sepecify that password must contain unique set of characters

LockoutOptions(User's lockout)

It contains the user lockout related settings. Followings are Lockout options supported by identity service.

  • DefaultLockoutTimeSpan : it is default time for that user is locked out

  • MaxFailedAccessAttempts : it specify maximum number of failed attempts

  • AllowedForNewUsers : it determines that new user can be locked out

SignInOptions(Sign in settings)

It contains the setting related to sign-in. Followings are SingnIn options supported by identity service.

  • RequireConfirmedEmail : it indicate that confirmed email address is required

  • RequireConfirmedPhoneNumber : it indicate that confirmed phone number is required

UserOptions (User validation settings)

It contains the setting related to user information. Followings are Useroptions supported by identity service

  • RequireUniqueEmail : It indicate that application required unique email address for the each user

  • AllowedUserNameCharacters : Using this setting , we can specify allowed character list in user name

TokenOption

It contains option related to user token provider. It has following options.

  • EmailConfirmationTokenProvider : it specify the token provider that used to generate tokens used in confirmation email

  • PasswordResetTokenProvider : it specify the token provide that is used in password reset email

  • ChangeEmailTokenProvider : it specify the token provide that is used in change confirmation email

  • ChangePhoneNumberTokenProvider :it specify the token provide that is used when phone number is changed

  • AuthenticatorTokenProvider : it specify the token provide that is used to validate two factor sign ins with an authenticator

ConfigureApplicationCookie (Cookie settings for Application)

It contains the setting related to application cookies. It has following options

  • Cookie.Name : name of the application cookie

  • Cookie.HttpOnly : it specify that cookie is accessible from client side scripts or not

  • ExpireTimeSpan : it specify that the time that authentication ticket stored in the cookie and remain valid.

  • LoginPath : Login page path

  • LogoutPath : Logout page path

  • AccessDeniedPath : it defined the path on which user will redirected if authorization is fail

  • SlidingExpiration : This is Boolean property. When it set to true, new cookie will be created when current cookie is more than halfway through the expiration window.

  • ReturnUrlParameter : it is used by middleware and it specify the return url

In the following example, I have override some of the configuration options as:

  • The password configuration that force user to enter at least one digit and minimum password length is 10

  • The Lockout configuration that override default lock time and maximum failed password attempts

  • The Sign-In configuration that force user enter confirmed email

  • The User configuration that allows only unique emails

  • The cookie configuration that override cookie expiration time, login url, logout url and Sliding Expiration setting

 
 public void ConfigureServices(IServiceCollection services)
 {
 …
 …
 
 services.AddDbContext<ApplicationDbContext>(options =>
 options.UseSqlServer(
 Configuration.GetConnectionString("DefaultConnection")));
 
 services.AddDefaultIdentity<IdentityUser>()
 .AddEntityFrameworkStores<ApplicationDbContext>();
 
 //Overide the configuration 
 services.Configure<IdentityOptions>(opt =>
 {
 // Password settings 
 opt.Password.RequireDigit = true;
 opt.Password.RequiredLength = 10;
 
 // Lockout settings 
 opt.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(60);
 opt.Lockout.MaxFailedAccessAttempts = 5;
 
 //Signin option
 opt.SignIn.RequireConfirmedEmail = false;
 
 // User settings 
 opt.User.RequireUniqueEmail = true;
 
 //Token Option
 opt.Tokens.AuthenticatorTokenProvider = "Name of AuthenticatorTokenProvider";
 
 });
 
 // Cookie settings 
 services.ConfigureApplicationCookie(options =>
 {
 options.Cookie.Expiration = TimeSpan.FromDays(150);
 options.Cookie.HttpOnly = true; 
 options.LoginPath = "/Account/Login";
 options.LogoutPath = "/Account/Logout";
 options.SlidingExpiration = true;
 });
 
 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
 }
 

Demo

To demonstration the concept, I have mark "About" action method of Home Controller with Authorize attribute so only authenticated user can able to access the method. If we trying to access the about page without login, system will redirect to Login page.

 
 [Authorize]
 public IActionResult About()
 {
 ViewData["Message"] = "Your application description page.";
 
 return View();
 }
 

Authentication is checking only the user identity and allows user to access the system resources. Authorization validates the user privileges to access a system resource. There are many types of authorization available with asp.net code such as simple authorization, role based, claims based and policy-based authorization. If we provide only authentication, it is called simple authorization. In this article, I will explain about the role-based authorization.

Role-based Authorization

Identity Membership system allow us to defined role for the user and with the help of user role, we can identify whether user has privilege to access the page or not.With default template, only UserManager class of Identity service is available but do the role-based authentication, RoleManager class is also required. Both the classes available together by using “AddIdentity” method that adds the identity configuration for specific role and user.

 public void ConfigureServices(IServiceCollection services)
 {
 ….
 ….
 services.AddIdentity<IdentityUser, IdentityRole>()
 .AddEntityFrameworkStores<ApplicationDbContext>();
 ….
 ….
 }
 

To demonstrate the concept, I have created hard-code roles and user. Here, I have created 4 Roles and 4 users and map each user with one role. You can create user and role by either inserting record to the database table or using membership classes and method you can add user and role.In following code snippet, I have created roles and users by using membership classes. This method is call from Configure method of startup class when all the services are loaded and available for use.

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
 {
 …
 …
 …
 app.UseMvc(routes =>
 {
 routes.MapRoute(
 name: "default",
 template: "{controller=Home}/{action=Index}/{id?}");
 });
 CreateUsersAndRoles(serviceProvider).Wait();
 }
 
 private async Task CreateUsersAndRoles(IServiceProvider serviceProvider)
 {
 //initializing custom roles 
 var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
 var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
 string[] roleNames = { "Administrator", "GroupUser", "User", "Guest" };
 IdentityResult roleResult;
 
 foreach (var roleName in roleNames)
 {
 var roleExist = await RoleManager.RoleExistsAsync(roleName);
 if (!roleExist)
 {
 //create the roles and seed them to the database: Question 1 
 roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
 }
 }
 
 IdentityUser user = await UserManager.FindByEmailAsync("jignesht@gmail.com");
 
 if (user == null)
 {
 user = new IdentityUser()
 {
 UserName = "jignesht@gmail.com",
 Email = "jignesht@gmail.com",
 };
 await UserManager.CreateAsync(user, "abC@123abca");
 }
 await UserManager.AddToRoleAsync(user, "Administrator");
 
 IdentityUser user1 = await UserManager.FindByEmailAsync("tejast@gmail.com");
 
 if (user1 == null)
 {
 user1 = new IdentityUser()
 {
 UserName = "tejast@gmail.com",
 Email = "tejast@gmail.com",
 };
 await UserManager.CreateAsync(user1, "xyZ@123abca");
 }
 await UserManager.AddToRoleAsync(user1, "GroupUser");
 
 IdentityUser user2 = await UserManager.FindByEmailAsync("rakesht@gmail.com");
 
 if (user2 == null)
 {
 user2 = new IdentityUser()
 {
 UserName = "rakesht@gmail.com",
 Email = "rakesht@gmail.com",
 };
 await UserManager.CreateAsync(user2, "pQr@123abca");
 }
 await UserManager.AddToRoleAsync(user2, "User");
 
 IdentityUser user3 = await UserManager.FindByEmailAsync("guest@gmail.com");
 
 if (user3 == null)
 {
 user3 = new IdentityUser()
 {
 UserName = "guest@gmail.com",
 Email = "guest@gmail.com",
 };
 await UserManager.CreateAsync(user3, "pQrz@123abca");
 }
 await UserManager.AddToRoleAsync(user3, "Guest");
 }
 

Using RoleManager and UserManager class of identity service, we can add the roles, users and user - role mapping. Using CreateAsync method of RoleManager class, we can create new roles. Using CreateAsync method of UserManager, we can create new user and using AddToRoleAsync method, we can add user in to particular role.

As describe in above section, we can do authorization using Authorize attribute. This attribute has Roles property in which we can specify the name of roles that allows to access. Using Following code, we can allows to access the action method to user that has "Administrator" role.

 [Authorize(Roles = "Administrator")]
 public IActionResult OnlyAdministratorAccess()
 {
 ViewData["userRole"] = "Administrator";
 return View("demo");
 }
 

The Authorize attribute can also be applied multiple times to the action method. By applying multiple attribute, we can allows multiples roles to access the action.

 [Authorize(Roles = "GroupUser")]
 [Authorize(Roles = "User")]
 public IActionResult MultipleRoleAccess1()
 {
 ViewData["userRole"] = "GroupUser,User";
 return View("demo");
 }
 

Alternatively, we can also specify multiple roles by passing comma separated values for roles. In following example, users in roles “GroupUser” and “User are allows to access the action method.

 [Authorize(Roles = "GroupUser,User")]
 public IActionResult MultipleRoleAccess1()
 {
 ViewData["userRole"] = "GroupUser,User";
 return View("demo");
 }
 
Summary

ASP.net core provide very rich identity membership service and using this service, we can add many facility such as login, logout, registration, etc. by writing few line of the code. This service will take care about many things such as validating tokens, finding users, add users, add user to role etc. We can also customize this features and also override the behavior of the Identity Option such as PasswordOption, SignInOption etc. Identity Membership system allow us to defined role for the user and with the help of user role, we can identify whether user has privilege to access the page or not. This is called Role-based authorization.

Hands-on Learning
Free Interview Books
 
+