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>
}