In the first part of the chapter, you saw how to handle user authentication and how to work with user logins. In the next part, you will see how to manage user access, which will allow you to fine-tune who has access to what.
The simplest authorization method is to use the [Authorize] meta decorator, which disables anonymous access completely. Users need to be signed in to be able to access restricted resources in this case.
Now, let's go and see how to implement it within the Tic-Tac-Toe application:
- Add a new method called SecuredPage to HomeController, and remove anonymous access to it by adding the [Authorize] decorator:
[Authorize] public async Task<IActionResult> SecuredPage() { return await Task.Run(() => { ViewBag.SecureWord = "Secured Page"; return View("SecuredPage"); }); }
- Add a new view called SecuredPage to the Views/Home folder:
@{ ViewData["Title"] = "Secured Page"; } @section Desktop {<h2>@Localizer["DesktopTitle"]</h2>} @section Mobile {<h2>@Localizer["MobileTitle"]</h2>} <div class="row"> <div class="col-lg-12"> <h2>Tic-Tac-Toe @ViewBag.SecureWord</h2> </div> </div>
- Try accessing the secured page by entering its URL, http://<host>/Home/SecuredPage, manually while not signed in. You will be redirected automatically to the Login page:
- Enter valid user credentials and sign in. You should be automatically redirected to the secured page and now be able to view it:
Another relatively popular approach is to use role-based security, which provides some more advanced features. It is one of the recommended methods for securing your ASP.NET Core 3 web applications.
The following example explains how to work with it:
- Add a new class called UserRoleModel to the Models folder, and make it inherited from IdentityUserRole<long>. This will be used by the built-in ASP.NET Core 3 Identity authentication features:
public class UserRoleModel : IdentityUserRole<Guid> { [Key] public long Id { get; set; } }
- Update the OnModelCreating method within GameDbContext:
protected override void OnModelCreating(ModelBuilder
modelBuilder) { ... modelBuilder.Entity<IdentityUserRole<Guid>>() .ToTable("UserRoleModel") .HasKey(x => new { x.UserId, x.RoleId }); }
- Open the NuGet Package Manager Console and execute the Add-Migration IdentityDb2 command. Then, execute the Update-Database command.
- Update UserService, and modify the constructor to create two roles called Player and Administrator, if they do not yet exist:
public UserService(RoleManager<RoleModel> roleManager,
ApplicationUserManager userManager, ILogger<UserService>
logger, SignInManager<UserModel> signInManager) { ... if (!roleManager.RoleExistsAsync("Player").Result) roleManager.CreateAsync(new RoleModel {
Name = "Player" }).Wait(); if (!roleManager.RoleExistsAsync("Administrator").Result)
roleManager.CreateAsync(new RoleModel {
Name = "Administrator" }).Wait(); }
- Update the RegisterUser method within UserService, and then add the user to the Player role or to the Administrator role during user registration:
... try { userModel.UserName = userModel.Email; var result = await _userManager.CreateAsync
(userModel,userModel.Password); if (result == IdentityResult.Success) { if(userModel.FirstName == "Jason") await _userManager.AddToRoleAsync(userModel,"Administrator"); else await _userManager.AddToRoleAsync(userModel, "Player"); } return result == IdentityResult.Success; } ...
- Start the application and register a new user, and then open the RoleModel table within SQL Server Object Explorer and analyze its content:
- Open the UserRoleModel table within SQL Server Object Explorer and analyze its content:
- Update the SignInUser method within UserService to map roles with claims:
... identity.AddClaim(new Claim("Score",
user.Score.ToString())); var roles = await _userManager.GetRolesAsync(user); identity.AddClaims(roles?.Select(r => new
Claim(ClaimTypes.Role, r))); await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = false }); ...
- Update the SecuredPage method within HomeController, use the administrator role to secure access, and then replace the Authorize decorator that was there initially with the following:
[Authorize(Roles = "Administrator")]
- Start the application. If you try to access http://<host>/Home/SecuredPage without being logged in, you will be redirected to the Login page. Sign in as a user who has the player role, and you will be redirected to an Access Denied page (which does not exist, hence the 404 error) since the user does not have the administrator role:
- Log out and then sign in as a user who has the administrator role. You should now see the secured page, since the user has the necessary role:
In the following example, you will see how to sign in automatically as a registered user and how to activate claims-based and policy-based authentication:
- Update the SignInUser method, and then add a new method called SignIn to UserService:
public async Task<SignInResult> SignInUser(LoginModel loginModel, HttpContext httpContext) { ... await SignIn(httpContext, user); return isValid; } catch (Exception ex) { ... } finally { ... } }
Implement the SignIn method as follows:
private async Task SignIn(HttpContext httpContext, UserModel user) { var identity = new ClaimsIdentity(CookieAuthenticationDefaults.
AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName)); identity.AddClaim(new Claim(ClaimTypes.GivenName,
user.FirstName)); identity.AddClaim(new Claim(ClaimTypes.Surname, user.LastName)); identity.AddClaim(new Claim("displayName", $"{user.FirstName}
{user.LastName}")); if (!string.IsNullOrEmpty(user.PhoneNumber)) identity.AddClaim(new Claim(ClaimTypes.HomePhone,
user.PhoneNumber)); identity.AddClaim(new Claim("Score", user.Score.ToString())); var roles = await _userManager.GetRolesAsync(user); identity.AddClaims(roles?.Select(r => new Claim(ClaimTypes.
Role, r))); if (user.FirstName == "Jason") identity.AddClaim(new Claim("AccessLevel", "Administrator")); await httpContext.SignInAsync(CookieAuthenticationDefaults.
AuthenticationScheme,
new ClaimsPrincipal(identity),new AuthenticationProperties {
IsPersistent = false }); }
- Update the RegisterUser method in UserService, add a new parameter to automatically sign in a user after registration, and then re-extract the user service interface:
public async Task<bool> RegisterUser(UserModel userModel,
bool isOnline = false) { ... if (result == IdentityResult.Success) { ... if (isOnline) { HttpContext httpContext =
new HttpContextAccessor().HttpContext; await Signin(httpContext, userModel); } } ... }
- Update the Index method in UserRegistrationController to automatically sign in a newly registered user:
... await _userService.RegisterUser(userModel, true); ...
- Update the ConfirmGameInvitation method in GameInvitationController to sign an invited user in automatically:
... await _userService.RegisterUser(new UserModel { Email = gameInvitation.EmailTo, EmailConfirmationDate = DateTime.Now, EmailConfirmed = true, FirstName = "", LastName = "", Password = "Azerty123!", UserName = gameInvitation.EmailTo }, true); ...
- Add a new policy called AdministratorAccessLevelPolicy to the Startup class, just after the MVC Middleware configuration:
services.AddAuthorization(options => { options.AddPolicy("AdministratorAccessLevelPolicy",
policy => policy.RequireClaim("AccessLevel",
"Administrator")); });
- Update the SecuredPage method within HomeController, using Policy instead of Role to secure access, and then replace the Authorize decorator:
[Authorize(Policy = "AdministratorAccessLevelPolicy")]
For this case, the Authorize decorator you have seen before allows you to define which middleware can authenticate a user.
Here is an example allowing cookies and a bearer token:
[Authorize(AuthenticationSchemes = "Cookie,Bearer",
Policy = "AdministratorAccessLevelPolicy")]
- Start the application, register a new user with an Administrator access level, sign in, and then access http://<host>/Home/SecuredPage. Everything should be working as before.
- Try accessing the secured page as a user who does not have the required access level; as before, you should be redirected to http://<host>/Account/AccessDenied?ReturnUrl=%2FHome%2FSecuredPage:
- Log out and then sign in as a user who has the Administrator role. You should now see the secured page since the user has the necessary role.