On a complex website, there will be many roles or policies that each role and policy have their own privileges. For example, assuming you only want people with admin roles to access the admin panel, this is called authorization with role. Another case would be assuming you only want users with age 18 or older to access some pages on the website, this is called authorization with policy.
You can download the example code used in this topic on GitHub.
To understand this guide, you need to have some basic knowledge:
As the name suggested, you authorize people with their roles. Assuming you have a resource and you define which role can access that resource, then any user with one or more roles in the role list can have access to the resource.
Create a new AdminPanel.razor
component in the Page
folder.
@page "/admin-panel" <AuthorizeView Roles="Admin,SuperAdmin"> <NotAuthorized> You must be Admin or Super Admin to access this page. </NotAuthorized> <Authorized> <h1>Admin panel</h1> </Authorized> </AuthorizeView>
In this example, you are specifying users with either "Admin" or "SuperAdmin" can access the page. Users without any of those roles won't be able to access the page.
You need to set the roles for the user in the ClaimPrincipal
object of GetAuthenticationStateAsync
method. Continuation from the example in Authentication and authorization. Add roles as strings to the claim as follows:
private ClaimsIdentity CreateIdentityFromUser(User user) { var result = new ClaimsIdentity(new Claim[] { new (ClaimTypes.Name, user.Username), new (ClaimTypes.Hash, user.Password), }, "BlazorSchool"); var roles = _dataProviderService.GetUserRoles(user); foreach(string role in roles) { result.AddClaim(new (ClaimTypes.Role, role)); } return result; }
After that, use the ClaimsIdentity
we just created in the above method to create a new AuthenticationState
in the method GetAuthenticationStateAsync
.
Users with one of the roles Admin or SuperAdmin can now access the AdminPanel.razor
component.
An essential feature of authorization by policy is to customize the authorization rules. The following steps demonstrate how to authorize users over 18.
public class AdultRequirement : IAuthorizationRequirement { public static string ClaimName => "Age"; }
public class AdultRequirementHandler : AuthorizationHandler<AdultRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdultRequirement requirement) { var (value, isSuccess) = TryRetrieveValue(context); if (isSuccess && value >= 18) { context.Succeed(requirement); } else { context.Fail(); } return Task.CompletedTask; } private static (int, bool) TryRetrieveValue(AuthorizationHandlerContext context) { string value = context.User.FindFirst(ct => ct.Type == AdultRequirement.ClaimName)?.Value; if (!string.IsNullOrEmpty(value)) { int age = Convert.ToInt32(value); return (age, true); } else { return (0, false); } } }
In this class, we will get the value of a claim with the name "Age" and compare that value to 18. If the condition is satisfactory, we call context.Succeed(requirement)
. Otherwise, we call context.Fail()
.
AuthorizationHandler
. The following code demonstrates how to authorize a user by comparing the user data to the authorizing resource data.public class OverAgeRequirementHandler : AuthorizationHandler<OverAgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OverAgeRequirement requirement) { var (value, isSuccess) = TryRetrieveValue(context); int minAge = Convert.ToInt32(context?.Resource ?? 0); if (isSuccess && value >= minAge) { context.Succeed(requirement); } else { context.Fail(); } return Task.CompletedTask; } private static (int, bool) TryRetrieveValue(AuthorizationHandlerContext context) { ... } }
In the AdultRequirementHandler
you just created, you will get data from the claim to authorize it. In order to do that, you need to put the data in the claim when a user logins. You need to update your CreateIdentityFromUser
method:
private ClaimsIdentity CreateIdentityFromUser(User user) { var result = new ClaimsIdentity(new Claim[] { ... new (AdultRequirement.ClaimName, user.Age.ToString()), new ("IsPremiumMember", user.IsPremiumMember.ToString()) }, "BlazorSchool"); ... }
In this method, we also include the IsPremiumMember
to demonstrate how to combine authorization rules later.
A policy is defined by a combination of rules. You can define many policies and use them across your website to authorize users. You need to define a policy in ConfigureServices
method in Startup.cs
.
public void ConfigureServices(IServiceCollection services) { ... services.AddAuthorization(config => { config.AddPolicy("CanBuyAlcohol", policy => { policy.AddRequirements(new AdultRequirement()); policy.RequireClaim("IsPremiumMember", true.ToString()); }); }); }
In this example, you are authorizing people for 2 conditions: they must be over 18 and must be a premium member. The first parameter of config.AddPolicy(name, configurePolicy)
method is the name of the policy, which is CanBuyAlcohol
and the second parameter is all the conditions of this policy. Later on, you will use the name CanBuyAlcohol
to use this policy.
Since AdultRequirement
is a custom rule, you must register its requirement handler as well.
services.AddSingleton<IAuthorizationHandler, AdultRequirementHandler>();
Once you have completed all the steps above, you will be able to use the CanBuyAlcohol
policy in any component on your website as long as it is wrapped by CascadingAuthenticationState
component in App.razor
. To use the policy, specify its name in the AuthorizeView
component as follows:
<AuthorizeView Policy="CanBuyAlcohol"> <Authorized> <h3>Welcome to the Alcohol Store</h3> </Authorized> <NotAuthorized> You don't have permission to see this. </NotAuthorized> </AuthorizeView>
If you need the resource data to authorize the user then you can pass resource data to the AuthorizeView
by using Resource
parameter. Then AuthorizeView
will pass that data to the AuthorizationHandler
. You can pass authorizing resource data as follows:
@foreach (Toy toy in SimulatedDataProviderService.Toys) { <h1>@toy.Name</h1> <AuthorizeView Policy="OverAge" Resource="toy.AgeRequired"> <Authorized> <button>Buy this toy</button> </Authorized> <NotAuthorized> Restricted </NotAuthorized> </AuthorizeView> }