After learning about the new Authorization Policy model in ASP.NET Core, our intrepid reporter Seth Juarez wanted to learn about more complicated ASP.NET Authorization policies. In the following video, he speaks with ASP.NET Security Analyst Barry Dorrans. Last time, Barry showed us how to get started with the new ASP.NET Policy model. Notes and links from their discussion follow.
Previously with ASP.NET you had to authorize based on the membership of a role or the value of a claim. In the new ASP.NET Core Policies, these security requirements can be expressed through code and enforce more complicated and more realistic authorization requirements.
Barry explained that claims are properties of an Identity that are not necessarily stored in a Cookie, but for the purposes of this demo they are and that makes them very easy to use in multi-webserver scenarios. Seth cautioned against storing entire data tables in a claim. Examples of good claims to store include name, birth date, email address. If you need more information from a claim, you could store a single value in a claim and then use that value to look up in a database using a Claims Transformation.
Barry used the example of the American minimum age to drink, 21 years old to help define authorizing users. He inserted his birth date as a claim record into the sample code, yes Barry’s birth date is June 8th – be sure to wish him a happy birthday. We’d like to make the policy work in other countries where the minimum age may be 16, 18 or some other age.
To enforce this requirement for a policy, the MinimumAgeRequirement class was created that implements the AuthorizationHandler<MinimumAgeRequirement> base class and the IAuthorizationRequirement marker interface. The Handle method provided by the base class is where the enforcement of this requirement takes place. The AuthorizationContext that is passed in to this method is used to mark if the acting user passes this requirement by calling the Succeed method. Other possible outcomes from this method are to not mark success on the context, indicating that the requirement is not met and allowing another class to attempt to handle it and finally you can mark the context with Fail that will stop all policy checks when there is a system error.
The new requirement is added to the policy by calling policy.Requirements.Add(new MinimumAgeRequirement(21)) in the Startup.ConfigureServices method.
In the real world, there may be multiple requirements that are logically OR’d together. The next sample highlights an office security scenario where people can be admitted entrance with either an employee badge, a visitor badge, or a temporary employee badge. In this case, the OfficeEntryRequirement is defined in one class and implements the marker IAuthorizationRequirement. There is no fancy logic for this, just a check for a badge and that will be implemented by one of several AuthorizationHandler classes. Barry implemented these as AuthorizationHandler<OfficeEntryRequirement> subclasses. In the HasBadgeHandler, the Handle method inspected the claim for the BadgeNumber and the issuer of the claim to verify that it meets the security requirements for the office. The second class was created to verify if a temporary badge was issued and the expiration date.
Barry demonstrated that a policy needs to be registered in the Startup.ConfigureServices methods and then the two handlers that were constructed need to be registered with the dependency injection framework so that they are available when testing the OfficeEntryRequirement. At the end of the same ConfigureServices method, Barry added two registrations that mapped IAuthorizationHandler to the Handler classes he previously created. These simple classes were registered as Singletons because they are simple and stateless.
In the multiple requirement handler scenario, all requirements will always be evaluated so that any side-effects of the requirement handlers like logging are still enabled.
Next, Barry and Seth looked at a demo about Resource-based Authorization. This allows you to grant access based on the action a user wants to take on some resource, perhaps a document, that the user may or may not have access to. This additional check can be enforced in AuthorizationHandler classes that implement a second type in the generic AuthorizationHandler<T,K> base class. The second type is the resource to inspect in order to determine access. However, the enforcement of the resource authorization can no longer take place in an Attribute, and needs to take place in an AuthorizationService. In the MVC controller, Barry showed a test using the AUthorizationService.AuthorizeAsync method and in the case of failure returning a ChallengeResult that will route the user to an appropriate 403 Forbidden page or requests for additional credentials.
Seth pointed out that this new design of authentication and authorization can be built completely separate from the application and added in where appropriate as the application is constructed. Additionally, this means that the policy and requirements code can be unit-tested.
Barry showed an additional sample where he injected the AuthorizationService into a razor view with the @inject directive and used the AuthorizationService to show and hide items in the user-interface. This does not prevent access to the resource, just hides the user-interface components and the Controller class still requires the authorization check in place to prevent someone, like Seth, from navigating directly to the hidden menu items.
Barry suggests you work through the workshop on his GitHub repository and take a look at the other samples he has available. Some are sarcastic, some are interesting samples that you could use. All of the security features for the new ASP.NET Core are documented at docs.asp.net