A workshop for moving through the various new pieces in ASP.NET Core Authorization
A workshop for moving through the various new pieces in ASP.NET Core Authorization
This is walk through for an ASP.NET Core Authorization Lab, now updated for ASP.NET Core 2.1 and VS2017. (If you're still using 1.x then the older version of the labs are available in the Core1x branch.)
This lab uses the Model-View-Controller template as that's what everyone has been using up until now and it's the most familiar starting point for the vast majority of people.
Official authorization documentation is at https://docs.asp.net/en/latest/security/authorization/index.html.
Tip: When you stop finish running the app at each stage always close the browser to clear the identity cookie.
AuthorizationLab and click OK.Startup.cs and add services.AddMvc(); to the top of the ConfigureServices() method;Configure() method, delete the existing code.Configure(); add the following code to setup MVC default routing;app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});Controllers folder HomeController.cs file in the Controllers directory, using the VS Controller template, or create it from scratch and ensuring inherits from Controller and has an Index() method which returns a View, for example
using Microsoft.AspNetCore.Mvc;
namespace AuthorizationLab.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}Views folder.Home folder under the Views.Index.cshtml file inside the Views\Home folder, and edit it to contain Hello World.Microsoft.AspNetCore.App meta package contains all the authentication and authorization packages, so you don't need to add any extra packages or references.startup.csapp.UseAuthentication(); at the top of the Configure() method.ConfigureServices() method.services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = new PathString("/Account/Login/");
options.AccessDeniedPath = new PathString("/Account/Forbidden/");
});[Authorize] attribute to the controller.Account controller, AccountController.cs in the Controllers folder. Create an Login() action and a Forbidden() action.using Microsoft.AspNetCore.Mvc;
namespace AuthorizationLab.Controllers
{
public class AccountController : Controller
{
public IActionResult Login()
{
return View();
}
public IActionResult Forbidden()
{
return View();
}
}
}Account folder under the Views folder and create corresponding views for the actions, Login.cshtml and Forbidden.cshtml. Account controller. Change the Login action to create a Principal and persist it using the code below. This will create user information and put it inside a cookie. This fakes what would normally happen in a forms based login system.…Home\Index view to display the name claim for the identity. Replace the contents of that view with the following code. This code is a little over complicated on purpose, as a user principal can contain more than one authenticated identity. This rarely happens, and frankly you'll know if you've written code to do this, but you should be aware of this edge case.@using System.Security.Claims;
@if (!User.Identities.Any(u => u.IsAuthenticated))
{
Hello World
}
else
{
Hello @User.Identities.First(
u => u.IsAuthenticated &&
u.HasClaim(c => c.Type == ClaimTypes.Name)).FindFirst(ClaimTypes.Name).Value
}claims.Add(new Claim(ClaimTypes.Name, "barry", ClaimValueTypes.String, Issuer)); inside the Login() method in the account controller is what is setting the name claim, and is what is being displayed by the view.Remember to close the browser to clear the identity cookie before moving on to the next step.
Authorize attribute from the Home controller.AddMvc() call in ConfigureServices() in Startup.cs to add a default authorization policy to the MVC configuration.services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});[AllowAnonymous] attribute to the Account controller, run again and see the user is logged in. AllowAnonymous allows you to mark a controller or an action method as not requiring authentication, even if you require authentication elsewhere.Remember to close the browser to clear the identity cookie before moving on to the next step.
Home controller and add an Authorize attribute with a role demand to either the controller, or the Index action method;[Authorize(Roles = "Administrator")]Forbidden view. This happens because you have an identity, but it's not part of the Administrator role.AccountController and add a second claims.Add() line, as shown below. This adds a Role claim with the value of Administrator to the issued identity.claims.Add(new Claim(ClaimTypes.Role, "Administrator", ClaimValueTypes.String, Issuer));Remember to close the browser to clear the identity cookie before moving on to the next step.
Startup.cs and locate the services.AddAuthentication() call in ConfigureServices() call.services.AddAuthentication() add a call to services.AddAuthorization() and create a simple policy as shown below.services.AddAuthorization(options =>
{
options.AddPolicy("AdministratorOnly", policy => policy.RequireRole("Administrator"));
});Authorize attribute parameters in Step 3. Authorize attribute to require a policy, rather than use the role parameter.[Authorize(Policy = "AdministratorOnly")]services.AddAuthorization(options =>
{
options.AddPolicy("AdministratorOnly", policy => policy.RequireRole("Administrator"));
options.AddPolicy("EmployeeId", policy => policy.RequireClaim("EmployeeId"));
});Login action in the Account Controller.claims.Add(new Claim("EmployeeId", string.Empty, ClaimValueTypes.String, Issuer));Authorize attribute to the Home controller, using the new policy name.[Authorize(Policy = "EmployeeId")]EmployeeId policy to require one of a number of values;options.AddPolicy("EmployeeId", policy => policy.RequireClaim("EmployeeId", "123", "456"));Login action, as shown below, and try again.claims.Add(new Claim("EmployeeId", "123", ClaimValueTypes.String, Issuer));If a policy has multiple claim requirements all the claim requirements must be fulfilled for authorization to succeed.
Remember to close the browser to clear the identity cookie before moving on to the next step.
Code based policies consist of a requirement, implementing IAuthorizationRequirement and a handler for the requirement, implementing AuthorizationHandler where T is the requirement.
Login action in the Account controller.claims.Add(new Claim(ClaimTypes.DateOfBirth, "1970-06-08", ClaimValueTypes.Date));…options.AddPolicy("Over21Only", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));Home controller using the Authorize attribute.Remember to close the browser to clear the identity cookie before moving on to the next step.
You may have noticed what a handler returns, nothing at all (Strictly we're returning Task.CompletedTask;, which is effectively nothing).
Handlers inform the authorization service they have succeeded by calling context.Succeed(requirement);.
You may be asking yourself if there is a context.Succeed() is there a context.Fail()? There is, but if your requirement isn't met you
shouldn't touch the context at all. Now you may be asking why not? Well ...
Sometimes you may want multiple handlers for an Authorization Requirement, for example when there are multiple ways to fulfill a requirement. Microsoft's office doors open with your Microsoft badge, however on days you forget your badge you can go to reception and get a temporary pass and the receptionist will let you through the gates. Thus there are two ways to fulfill the single entry requirement. In the ASP.N
No open issues yet, or sync has not completed.