0% found this document useful (0 votes)
19 views75 pages

Chapter 16 Slides

Uploaded by

aisha.adediran0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views75 pages

Chapter 16 Slides

Uploaded by

aisha.adediran0
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 75

Chapter 16

How to
authenticate and
authorize users

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 1
Objectives (part 1)
Applied
1. Restrict access to some or all pages of a web app.
2. Allow users who have registered and logged in to access the
restricted pages that they are authorized to access.

Knowledge
3. Distinguish between authentication and authorization.
4. Describe how individual user account authentication works.
5. Describe how to use authorization attributes to restrict access to
controllers and actions.
6. List and describe three properties of the IdentityUser class.
7. Describe how to add the Identity tables to the DB context class and
the database.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 2
Objectives (part 2)
6. Describe how to add the Identity service to the HTTP request and
response pipeline, including how to configure password options.
7. Describe how to inject a SignInManager<T> object into a layout
and use it to add Log In/Out buttons depending on whether the
user is currently logged in.
8. Describe how to inject the UserManager<T>, SignInManager<T>,
and RoleManager<T> objects into the Account controller.
9. Describe how to use the UserManager<T> class to create, update,
and delete users.
10. Describe how to use the SignInManager<T> class to log users in
and out.
11. Define roles and describe how they are used for authorization.
12. Describe how to use the RoleManager<T> and UserManager<T>
classes to work with roles.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 3
Windows-based authentication
 Causes the browser to display a login dialog box when the user
attempts to access a restricted page.
 Is supported by most browsers.
 Is configured through the IIS management console.
 Uses Windows user accounts and directory rights to grant access
to restricted pages.
 Is most appropriate for an intranet app.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 4
Individual user account authentication
 Allows developers to code a login page that gets the username
and password.
 Encrypts the username and password entered by the user if the
login page uses a secure connection.
 Doesn’t rely on Windows user accounts.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 5
Third-party authentication services
 Is provided by third parties such as Google, Facebook, Twitter,
and Microsoft using technologies like OpenID and OAuth.
 Allows users to use their existing logins and frees developers
from having to worry about the secure storage of user
credentials.
 Can issue identities or accept identities from other web apps and
access user data on other services.
 Can use two-factor authentication.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 6
HTTP requests and responses

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 7
Some classes provided by ASP.NET Identity
IdentityDbContext
IdentityUser
IdentityRole
UserManager
RoleManager
SignInManager
IdentityResult

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 8
Some benefits of Identity
 It can be used with all ASP.NET frameworks.
 You have control over the schema of the data store that holds
user information, and you can change the storage system from
the default of SQL Server.
 It’s modular, so it’s easier to unit test.
 It supports claims-based authentication, which can be more
flexible than using simple roles.
 It supports third-party authentication providers.
 It’s based on OWIN (Open Web Interface for .NET) middleware.
 It’s distributed as a NuGet package, so Microsoft can deliver new
features and bug fixes faster than before.

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 9
Attributes for authorization
AllowAnonymous
Authorize
Authorize(Roles = "r1, r2")

Using directive for the Authorization namespace


using Microsoft.AspNetCore.Authorization;

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 10
Only allow logged in users
to access an entire controller
[Authorize]
public class CartController : Controller {
...
}

Only allow logged in users in the Admin role


to access an entire controller
[Authorize(Roles = "Admin")]
public class BookController : Controller {
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 11
Different attributes for different action methods
public class ProductController : Controller
{
...
[AllowAnonymous]
[HttpGet]
public IActionResult List() {
...
}

[Authorize]
[HttpGet]
public IActionResult Add()
{
...
}

[Authorize(Roles = "Admin")]
[HttpGet]
public IActionResult Delete(int id)
{
...
}
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 12
The NuGet package for Identity with EF Core
Microsoft.AspNetCore.Identity.EntityFrameworkCore

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 13
Some properties of the IdentityUser class
UserName
Password
ConfirmPassword
Email
EmailConfirmed
PhoneNumber
PhoneNumberConfirmed

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 14
The User entity class
using Microsoft.AspNetCore.Identity;

namespace Bookstore.Models
{
public class User : IdentityUser {
// Inherits all IdentityUser properties
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 15
The Bookstore context class (part 1)
...
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

namespace Bookstore.Models
{
public class BookstoreContext : IdentityDbContext<User>
{
public BookstoreContext(
DbContextOptions<BookstoreContext> options)
: base(options) { }

public DbSet<Author> Authors { get; set; }


public DbSet<Book> Books { get; set; }
public DbSet<BookAuthor> BookAuthors { get; set; }
public DbSet<Genre> Genres { get; set; }

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 16
The Bookstore context class (part 2)
protected override void OnModelCreating(
ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// BookAuthor: set primary key


modelBuilder.Entity<BookAuthor>()
.HasKey(ba => new { ba.BookId, ba.AuthorId });
...
}
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 17
How to add Identity tables to the database
1. Start the Package Manager Console (PMC).
2. Add a migration that adds the tables by entering a command
like this one:
Add-Migration AddIdentityTables
3. Update the database by entering a command like this one:
Update-Database

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 18
The Up() method of the generated migration class
(part 1)
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName =
table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 19
The Up() method (part 2)
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName =
table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail =
table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false),
Discriminator = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
...

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 20
The using directive for the Identity namespace
using Microsoft.AspNetCore.Identity;

How to add the Identity service


with default password options
public void ConfigureServices(
IServiceCollection services)
{
...
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<BookstoreContext>()
.AddDefaultTokenProviders();
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 21
Some properties of the PasswordOptions class
RequiredLength
RequireLowercase
RequireUppercase
RequireDigit
RequireNonAlphanumeric

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 22
How to configure password options
public void ConfigureServices(
IServiceCollection services)
{
...
services.AddIdentity<User, IdentityRole>(options => {
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireDigit = false;
}).AddEntityFrameworkStores<BookstoreContext>()
.AddDefaultTokenProviders();
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 23
How to configure your app to use authentication
and authorization
public void Configure(IAppBuilder app,
IWebHostEnvironment env)
{
...
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseSession();
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 24
The Register link and Log In button in the navbar

The Log Out button in the navbar

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 25
Some of the code for the navbar (part 1)
<!-- Home, Books, Authors, and Cart links go here -->

@using Microsoft.AspNetCore.Identity
@inject SignInManager<User> signInManager
@if (signInManager.IsSignedIn(User))
{
// signed-in user - Log Out button and username
<li class="nav-item">
<form method="post" asp-action="Logout"
asp-controller="Account" asp-area="">
<input type="submit" value="Log Out"
class="btn btn-outline-light" />
<span class="text-light">@User.Identity.Name</span>
</form>
</li>
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 26
Some of the code for the navbar (part 2)
else
{
// get current action
var action = ViewContext.RouteData.Values["action"]?.ToString();

// anonymous user - Register link and Log In button


<li class="nav-item @Nav.Active("Register", action)">
<a asp-action="Register" asp-controller="Account"
asp-area="" class="nav-link">Register</a>
</li>
<li class="nav-item">
<a asp-action="Login" asp-controller="Account"
asp-area="" class="btn btn-outline-light">Log In</a>
</li>
}

<!-- Admin link goes here -->

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 27
Some starting code for the Account controller
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Bookstore.Models;

namespace Bookstore.Controllers
{
public class AccountController : Controller
{
private UserManager<User> userManager;
private SignInManager<User> signInManager;

public AccountController(UserManager<User> userMngr,


SignInManager<User> signInMngr)
{
userManager = userMngr;
signInManager = signInMngr;
}

// The Register(), LogIn(), and LogOut()methods go here


}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 28
The URL that MVC redirects to
for an unauthenticated request
/account/login

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 29
The Register page

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 30
The Register view model
using System.ComponentModel.DataAnnotations;

namespace Bookstore.Models
{
public class RegisterViewModel
{
[Required(ErrorMessage = "Please enter a username.")]
[StringLength(255)]
public string Username { get; set; }

[Required(ErrorMessage = "Please enter a password.")]


[DataType(DataType.Password)]
[Compare("ConfirmPassword")]
public string Password { get; set; }

[Required(ErrorMessage = "Please confirm your password.")]


[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
public string ConfirmPassword { get; set; }
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 31
The Register() action method for GET requests
[HttpGet]
public IActionResult Register()
{
return View();
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 32
The Account/Register view (part 1)
@model RegisterViewModel
@{
ViewBag.Title = "Register";
}

<h1>Register</h1>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form method="post" asp-action="Register">
<div class="form-group row">
<div class="col-sm-2"><label>Username:</label></div>
<div class="col-sm-4">
<input asp-for="Username" class="form-control" />
</div>
<div class="col">
<span asp-validation-for="Username"
class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"><label>Password:</label></div>
<div class="col-sm-4">
<input type="password" asp-for="Password"
class="form-control" />
</div>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 33
The Account/Register view (part 2)
<div class="col">
<span asp-validation-for="Password"
class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"><label>Confirm Password:</label></div>
<div class="col-sm-4">
<input type="password" asp-for="ConfirmPassword"
class="form-control" />
</div>
</div>
<div class="row">
<div class="offset-2 col-sm-4">
<button type="submit" class="btn btn-primary">
Register</button>
</div>
</div>
<div class="row">
<div class="offset-2 col-sm-4">
Already registered? <a asp-action="LogIn">Log In</a>
</div>
</div>
</form>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 34
Three methods of the UserManager class
CreateAsync(user)
UpdateAsync(user)
DeleteAsync(user)

Two methods of the SignInManager class


SignInAsync(user, isPersistent)
SignOutAsync()

Two properties of the IdentityResult class


Succeeded
Errors

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 35
The Register() action method for POST requests
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid) {
var user = new User { UserName = model.Username };
var result = await userManager.CreateAsync(user,
model.Password);
if (result.Succeeded) {
await signInManager.SignInAsync(user,
isPersistent: false);
return RedirectToAction("Index", "Home");
}
else {
foreach (var error in result.Errors) {
ModelState.AddModelError("", error.Description);
}
}
}
return View(model);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 36
The LogOut() action method for POST requests
[HttpPost]
public async Task<IActionResult> LogOut() {
await signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 37
The Login page

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 38
The Login view model
using System.ComponentModel.DataAnnotations;

namespace Bookstore.Models
{
public class LoginViewModel
{
[Required(ErrorMessage = "Please enter a username.")]
[StringLength(255)]
public string Username { get; set; }

[Required(ErrorMessage = "Please enter a password.")]


[StringLength(255)]
public string Password { get; set; }

public string ReturnUrl { get; set; }

public bool RememberMe { get; set; }


}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 39
The LogIn() action method for GET requests
[HttpGet]
public IActionResult LogIn(string returnURL = "")
{
var model = new LoginViewModel { ReturnUrl = returnURL };
return View(model);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 40
The Login view (part 1)
@model LoginViewModel
@{
ViewBag.Title = "Login";
}

<h1>Login</h1>

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


<form method="post" asp-action="LogIn"
asp-route-returnUrl="@Model.ReturnUrl">
<div class="form-group row">
<div class="col-sm-2"><label>Username:</label></div>
<div class="col-sm-4">
<input asp-for="Username" class="form-control" />
</div>
<div class="col">
<span asp-validation-for="Username"
class="text-danger"></span>
</div>
</div>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 41
The Login view (part 2)
<div class="form-group row">
<div class="col-sm-2"><label>Password:</label></div>
<div class="col-sm-4">
<input type="password" asp-for="Password"
class="form-control" />
</div>
<div class="col">
<span asp-validation-for="Password"
class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-2 col-sm-4">
<input type="checkbox" title="Remember Me"
asp-for="RememberMe" class="form-check" />
<label>Remember Me</label>
</div>
</div>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 42
The Login view (part 3)
<div class="row">
<div class="offset-2 col-sm-4">
<button type="submit" class="btn btn-primary">Log In
</button>
</div>
</div>
<div class="row">
<div class="offset-2 col-sm-4">
Not registered?
<a asp-action="Register">Register as a new user</a>
</div>
</div>
</form>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 43
Another method of the SignInManager class
PasswordSignInAsync(username, password, isPersistent,
lockoutOnFailure)

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 44
The LogIn() action method for POST requests
[HttpPost]
public async Task<IActionResult> LogIn(LoginViewModel model)
{
if (ModelState.IsValid) {
var result = await signInManager.PasswordSignInAsync(
model.Username, model.Password,
isPersistent: model.RememberMe,
lockoutOnFailure: false);

if (result.Succeeded) {
if (!string.IsNullOrEmpty(model.ReturnUrl) &&
Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
}
ModelState.AddModelError("", "Invalid username/password.");
return View(model);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 45
A property of the RoleManager class
Roles

Some of the methods of the RoleManager class


FindByIdAsync(id)
FindByNameAsync(name)
CreateAsync(role)
UpdateAsync(role)
DeleteAsync(role)

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 46
Another property of the UserManager class
Users

More methods of the UserManager class


FindByIdAsync(id)
FindByNameAsync(name)
IsInRoleAsync(user, roleName)
AddToRoleAsync(user, roleName)
RemoveFromRoleAsync(user, roleName)
GetRolesAsync(user)

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 47
Code that loops through all users and their roles
foreach (User user in userManager.Users) {
foreach (IdentityRole role in roleManager.Roles) {
if (await userManager.IsInRoleAsync(user,
role.Name)) {
// perform some processing if user is in role
}
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 48
Code that creates a role named Admin
var result = await roleManager.CreateAsync(
new IdentityRole("Admin"));
if (result.Succeeded) { ... }

Code that deletes a role


IdentityRole role = await roleManager.FindByIdAsync(id);
var result = await roleManager.DeleteAsync(role);
if (result.Succeeded) { ... }

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 49
Code that adds a user to the Admin role
User user = await userManager.FindByIdAsync(id);
var result = await userManager.AddToRoleAsync(user,
"Admin");
if (result.Succeeded) { ... }

Code that removes a user from the Admin role


User user = await userManager.FindByIdAsync(id);
var result = await userManager.RemoveFromRoleAsync(user,
"Admin");
if (result.Succeeded) { ... }

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 50
The Manage Users page

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 51
The updated User entity
using System.ComponentModel.DataAnnotations.Schema;
...
public class User : IdentityUser
{
[NotMapped]
public IList<string> RoleNames { get; set; };
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 52
The User view model
public class UserViewModel
{
public IEnumerable<User> Users { get; set; }
public IEnumerable<IdentityRole> Roles { get; set; }
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 53
The User controller and its Index() action method
(part 1)
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Bookstore.Models;
...
[Authorize(Role = "Admin")]
[Area("Admin")]
public class UserController : Controller
{
private UserManager<User> userManager;
private RoleManager<IdentityRole> roleManager;

public UserController(UserManager<User> userMngr,


RoleManager<IdentityRole> roleMngr)
{
userManager = userMngr;
roleManager = roleMngr;
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 54
The User controller and its Index() action method
(part 2)
public async Task<IActionResult> Index()
{
List<User> users = new List<User>();
foreach (User user in userManager.Users)
{
user.RoleNames = await userManager.GetRolesAsync(user);
users.Add(user);
}
UserViewModel model = new UserViewModel
{
Users = users,
Roles = roleManager.Roles
};
return View(model);
}

// the other action methods


}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 55
The User/Index view (part 1)
@model UserViewModel
@{
ViewData["Title"] = " | Manage Users";
}

<h1 class="mb-2">Manage Users</h1>

<h5 class="mt-2"><a asp-action="Add">Add a User</a></h5>

<table class="table table-bordered table-striped table-sm">


<thead>
<tr><th>Username</th><th>Roles</th>
<th></th><th></th><th></th>
</tr>
</thead>
<tbody>
@if (Model.Users.Count() == 0)
{
<tr><td colspan="5">There are no user accounts.</td></tr>
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 56
The User/Index view (part 2)
else
{
@foreach (User user in Model.Users)
{
<tr>
<td>@user.UserName</td>
<td>
@foreach (string roleName in user.RoleNames)
{
<div>@roleName</div>
}
</td>
<td>
<form method="post" asp-action="Delete"
asp-route-id="@user.Id">
<button type="submit" class="btn btn-primary">
Delete User</button>
</form>
</td>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 57
The User/Index view (part 3)
<td>
<form method="post" asp-action="AddToAdmin"
asp-route-id="@user.Id">
<button type="submit" class="btn btn-primary">
Add To Admin</button>
</form>
</td>
<td>
<form method="post" asp-action="RemoveFromAdmin"
asp-route-id="@user.Id">
<button type="submit" class="btn btn-primary">
Remove From Admin</button>
</form>
</td>
</tr>
}
}
</tbody>
</table>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 58
The User/Index view (part 4)
<h1 class="mb-2">Manage Roles</h1>

@if (Model.Roles.Count() == 0)
{
<form method="post" asp-action="CreateAdminRole">
<button type="submit" class="btn btn-primary">
Create Admin Role</button>
</form>
}
else
{
<table class="table table-bordered table-striped table-sm">
<thead>
<tr><th>Role</th><th></th></tr>
</thead>
<tbody>
@foreach (var role in Model.Roles)
{
<tr>
<td>@role.Name</td>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 59
The User/Index view (part 5)
<td>
<form method="post" asp-action="DeleteRole"
asp-route-id="@role.Id">
<button type="submit"
class="btn btn-primary">Delete Role
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 60
Other action methods of the User controller
(part 1)
[HttpPost]
public async Task<IActionResult> Delete(string id)
{
User user = await userManager.FindByIdAsync(id);
if (user != null) {
IdentityResult result =
await userManager.DeleteAsync(user);
if (!result.Succeeded) { // if failed
string errorMessage = "";
foreach (IdentityError error in result.Errors) {
errorMessage += error.Description + " | ";
}
TempData["message"] = errorMessage;
}
}
return RedirectToAction("Index");
}

// the Add() methods work like the Register() methods

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 61
Other action methods (part 2)
[HttpPost]
public async Task<IActionResult> AddToAdmin(string id)
{
IdentityRole adminRole =
await roleManager.FindByNameAsync("Admin");
if (adminRole == null) {
TempData["message"] = "Admin role does not exist. "
+ "Click 'Create Admin Role' button to create it.";
}
else {
User user = await userManager.FindByIdAsync(id);
await userManager.AddToRoleAsync(user, adminRole.Name);
}
return RedirectToAction("Index");
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 62
Other action methods (part 3)
[HttpPost]
public async Task<IActionResult> RemoveFromAdmin(string id)
{
User user = await userManager.FindByIdAsync(id);
await userManager.RemoveFromRoleAsync(user, "Admin");
return RedirectToAction("Index");
}

[HttpPost]
public async Task<IActionResult> DeleteRole(string id)
{
IdentityRole role = await roleManager.FindByIdAsync(id);
await roleManager.DeleteAsync(role);
return RedirectToAction("Index");
}

[HttpPost]
public async Task<IActionResult> CreateAdminRole()
{
await roleManager.CreateAsync(new IdentityRole("Admin"));
return RedirectToAction("Index");
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 63
Using directive for the Authorization attributes
using Microsoft.AspNetCore.Authorization;

The Cart controller requires users to be logged in


[Authorize]
public class CartController : Controller {
...
}

All controllers in the Admin area require users


to be in the Admin role
[Authorize(Roles = "Admin")]
[Area("Admin")]
public class BookController : Controller {
...
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 64
The AccessDenied() action method
of the Account controller
public ViewResult AccessDenied()
{
return View();
}

The code for the Account/AccessDenied view


@{
ViewBag.Title = "Access Denied";
}
<h2>Access Denied</h2>

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 65
The Account/AccessDenied view

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 66
A method added to the DB context class (part 1)
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
...
public static async Task CreateAdminUser(
IServiceProvider serviceProvider)
{
UserManager<User> userManager =
serviceProvider.GetRequiredService<UserManager<User>>();
RoleManager<IdentityRole> roleManager = serviceProvider
.GetRequiredService<RoleManager<IdentityRole>>();

string username = "admin";


string password = "Sesame";
string roleName = "Admin";

// if role doesn't exist, create it


if (await roleManager.FindByNameAsync(roleName) == null)
{
await roleManager.CreateAsync(new IdentityRole(roleName));
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 67
A method added to the DB context class (part 2)
// if username doesn't exist, create it and add it to role
if (await userManager.FindByNameAsync(username) == null)
{
User user = new User { UserName = username };
var result = await userManager.CreateAsync(user, password);
if (result.Succeeded)
{
await userManager.AddToRoleAsync(user, roleName);
}
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 68
A statement in Startup.cs that calls the method
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env)
{
// all other configuration statements

BookstoreContext.CreateAdminUser(app.ApplicationServices)
.Wait();
}

An option in the Program.cs file


that allows the method to execute
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseDefaultServiceProvider(
options => options.ValidateScopes = false);

});

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 69
The User/Change Password view

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 70
The User/ChangePassword view model
using System.ComponentModel.DataAnnotations;

namespace Bookstore.Models
{
public class ChangePasswordViewModel
{
public string Username { get; set; }

[Required(ErrorMessage = "Please enter password.")]


public string OldPassword { get; set; }

[Required(ErrorMessage = "Please enter new password.")]


[DataType(DataType.Password)]
public string NewPassword { get; set; }
}
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 71
The ChangePassword() action method for POSTs
[HttpPost]
public async Task<IActionResult> ChangePassword(
ChangePasswordViewModel model)
{
if (ModelState.IsValid)
{
User user =
await userManager.FindByNameAsync(model.Username);
var result = await userManager.ChangePasswordAsync(user,
model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
foreach (IdentityError error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
}
return View(model);
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 72
The updated User class
public class User : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }

[NotMapped]
public IList<string> RoleNames { get; set; };
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 73
The updated Register view model (part 1)
public class RegisterViewModel
{
[Required(ErrorMessage = "Please enter a username.")]
[StringLength(255)]
public string Username { get; set; }

[Required(ErrorMessage = "Please enter a first name.")]


[StringLength(255)]
public string FirstName { get; set; }

[Required(ErrorMessage = "Please enter a last name.")]


[StringLength(255)]
public string Lastname { get; set; }

[Required(ErrorMessage = "Please enter an email address.")]


[DataType(DataType.EmailAddress)]
public string Email { get; set; } // from IdentityUser class

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 74
The updated Register view model (part 2)
[Required(ErrorMessage = "Please enter a password.")]
[DataType(DataType.Password)]
[Compare("ConfirmPassword")]
public string Password { get; set; }

[Required(ErrorMessage = "Please confirm your password.")]


[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
public string ConfirmPassword { get; set; }
}

© 2020, Mike Murach & Associates, Inc.


Murach's ASP.NET Core MVC C16, Slide 75

You might also like