This tutorial will walk you through step by step on how to authorize a user, display the UI for each user differently based on their identity. In this tutorial, you will discover:
You can download the example code used in this topic on GitHub.
There are 2 approaches when authorizing a user:
This approach assumes every route in your website requires the user to be authenticated. Some routes can require more than authenticated user like require the user must have admin role, must be an adult, etc. You can also specify those requirements explicitly or remove the requirements entirely for each route eventually.
A route is bound to an independent component, that means if one of a route in the component requires some requirements, all the requirements will be applied to all other routes in that component.
With this approach, you will authorize your component as a whole. You can also combine authorize on route with authorize on individual component to authorize a part of UI.
The following image illustrates the authorize on route approach.
This approach can segregate a component to many parts and display them differently per user. The following image illustrates the authorize on individual component approach.
App.razor
. Replace RouteView
with AuthorizeRouteView
. For example:<CascadingAuthenticationState> <Router AppAssembly="typeof(App).Assembly"> <Found Context="routeData"> <FocusOnNavigate RouteData="routeData" Selector="h1" /> <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"></AuthorizeRouteView> </Found> <NotFound> ... </NotFound> </Router> </CascadingAuthenticationState>
NotAuthorized
and Authorizing
projected content inside the AuthorizeRouteView
component. For example:<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"> <NotAuthorized> <div>You are not authorized.</div> </NotAuthorized> <Authorizing> <div>Authorizing...</div> </Authorizing> </AuthorizeRouteView>
_Imports.razor
.@using Microsoft.AspNetCore.Authorization @attribute [Authorize]
@attribute [AllowAnonymous]
Wrap the content that needs to be authorized in the AuthorizeView
component. For example:
<AuthorizeView> <Authorized><div>Authenticated</div></Authorized> <Authorizing><div>Loading...</div></Authorizing> <NotAuthorized><div>Not authenticated</div></NotAuthorized> </AuthorizeView>
A resource (route, component, button, etc) is protected by an authorization rule, when a user satisfied all the requirements in an authorization rule, the user will be able to access the resource. There are 2 types of authorization rule:
The resource will be protected by a list of roles. When the user belongs to one of the role in the list, the rule is satisfied and the user can access the resource. The following steps will help you to use the role based rule.
GetAuthenticationStateAsync
. For example:public override Task<AuthenticationState> GetAuthenticationStateAsync() { var claims = new List<Claim> { new (ClaimTypes.NameIdentifier,"Blazor School"), new (ClaimTypes.Role,"User"), new (ClaimTypes.Role,"PremiumUser") }; ... }
You must use System.Security.Claims.ClaimTypes.Role
as the type of the claim.
Sample code for authorize on individual component approach:
<AuthorizeView Roles="User"> <Authorized> <div>You are a user</div> </Authorized> <NotAuthorized> <div>You are not a user</div> </NotAuthorized> </AuthorizeView>
You can also pass a list of roles to the AuthorizeView
as follows:
<AuthorizeView Roles="BlazorSchoolSupporter,Admin"> ... </AuthorizeView>
Sample code for authorize on route approach:
@attribute [Authorize(Roles="BlazorSchoolSupporter")]
You can also pass a list of roles to the Authorize
attribute as follows:
@attribute [Authorize(Roles="BlazorSchoolSupporter,Admin")]
The policy based rule provides more flexible in authorization, you can use built-in requirements or define your custom requirements. For example, the user must be an adult, paid user to see some resources.
The policy based rule has 2 parts:
The requirement will yield the static data to authorize the user. For example, to consider a user is an adult, the user must be 18 or older. 18 in this case is the static data to authorize, and you can declare a property in the requirement to yield value 18. If you don't have any static data to authorize, you still need to create a requirement.
The requirement handler is bound to a requirement, and responsible to check if the user satisfies the rule or not. For example, you have the number 18 in the requirement, the requirement handler will read the user's age and compare the user's age to 18.
The following steps will help you to use the policy based rule.
GetAuthenticationStateAsync
. For example:public override Task<AuthenticationState> GetAuthenticationStateAsync() { var claims = new List<Claim> { new (ClaimTypes.NameIdentifier,"Blazor School"), new ("Age", 17.ToString()) }; ... }
IAuthorizationRequirement
interface. For example:public class AdultRequirement : IAuthorizationRequirement { public int MinimumAgeToConsiderAnAdult { get; set; } = 18; }
AuthorizationHandler
class. For example:public class AdultRequirementHandler : AuthorizationHandler<AdultRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdultRequirement requirement) { int userAge = Convert.ToInt32(context.User.Claims.First(c => c.Type == "Age").Value); if(userAge >= requirement.MinimumAgeToConsiderAnAdult) { context.Succeed(requirement); } else { context.Fail(); } return Task.CompletedTask; } }
Program.cs
. For example:builder.Services.AddScoped<IAuthorizationHandler, AdultRequirementHandler>();
Program.cs
. For example:builder.Services.AddAuthorizationCore(config => { config.AddPolicy("AdultPolicy", policy => policy.AddRequirements(new AdultRequirement())); });
You can also add more requirements to a policy as follows:
builder.Services.AddAuthorizationCore(config => { config.AddPolicy("AdultAdminPolicy", policy => { policy.AddRequirements(new AdultRequirement()); policy.RequireRole("Admin"); }); });
Sample code for authorize on individual component approach:
<AuthorizeView Policy="AdultPolicy"> <Authorized> <div>You are an adult</div> </Authorized> <NotAuthorized> <div>You are not an adult</div> </NotAuthorized> </AuthorizeView>
Sample code for authorize on route approach:
@attribute [Authorize(Policy = "AdultPolicy")]
Sometimes, you need the data from the resource to authorize the user. For example, you are selling a list of books, and you only allow the users that are already bought the book to read. You will need to pass the book ID to the requirement handler, then the requirement handler will verify if the user has bought the book or not.
public class UnderAgeRestrictedRequirement : IAuthorizationRequirement { }
public class UnderAgeRestrictedRequirementHandler : AuthorizationHandler<UnderAgeRestrictedRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UnderAgeRestrictedRequirement requirement) { int userAge = Convert.ToInt32(context.User.Claims.First(c => c.Type == "Age").Value); int minimumAgeRequired = Convert.ToInt32(context.Resource); if(userAge < minimumAgeRequired) { context.Fail(new (this, "User is underage")); } else { context.Succeed(requirement); } return Task.CompletedTask; } }
Program.cs
. For example:builder.Services.AddScoped<IAuthorizationHandler, UnderAgeRestrictedRequirementHandler>();
Program.cs
. For example:builder.Services.AddAuthorizationCore(config => { config.AddPolicy("UnderAgePolicy", policy => policy.AddRequirements(new UnderAgeRestrictedRequirement())); });
AuthorizeView
.<AuthorizeView Policy="UnderAgePolicy" Resource="10"> <Authorized> <div>You are more than 10</div> </Authorized> <NotAuthorized> <div>You are not more than 10</div> </NotAuthorized> </AuthorizeView>
The authorize on route approach does not support passing data to the requirement handler.
In this section, we have collected some common mistakes from the Blazor School Discord Community.
FocusOnNavigate
component in the App.razor
When you are implementing the authorize on route approach, you will need to change your App.razor
component. Remember to keep the FocusOnNavigate
component when you are implementing this approach if you are not intended to remove it.
When you are implementing the authorize on route approach, you need to add [AllowAnonymous]
attribute to your login page to let the user login to the website. Otherwise, the users have no way to access your website.