✅ Similar to Blazor Server
Implementing authorization in Blazor is an essential aspect of building secure web applications. Blazor provides various authorization features such as route and component-level authorization, role-based authorization, and policy-based authorization. This tutorial covers the following topics to guide you through the process of implementing authorization in your Blazor application:
You can download the example code used in this topic on GitHub.
Blazor provides two approaches to implementing authorization:
Route-level authorization enables you to control access to an entire page or set of pages based on user roles or policies. Component-level authorization allows you to control access to individual components within a page based on the same criteria.
By using these two approaches in combination, you can implement fine-grained access control over your Blazor application, ensuring that users only have access to the resources they are authorized to view or modify.
Route-level authorization in Blazor assumes that every route in your web application requires the user to be authenticated. However, you can also specify additional requirements for certain routes, such as requiring the user to have an admin role or be of a certain age. You can either explicitly specify these requirements or remove them entirely for each route.
Since a route is bound to an independent component, any requirements specified for a single route will be applied to all other routes within that component.
With route-level authorization, you can authorize your components as a whole. You can also combine this approach with component-level authorization to control access to specific parts of the UI.
The following image illustrates how the route-level authorization approach works:
Component-level authorization allows you to control access to individual components within a page, which means that you can display different parts of a component to different users based on their roles or policies. The following image illustrates how the authorization on an individual component approach works:
The choice between route-level authorization and component-level authorization depends on your specific requirements and the level of control you need over access to your application resources.
Route-level authorization is suitable if you need to control access to entire pages or sets of pages based on user roles or policies. This approach is useful for applications where all users have the same level of access to a page or set of pages.
On the other hand, component-level authorization is ideal if you need fine-grained control over access to individual components within a page. This approach allows you to display different parts of a component to different users based on their roles or policies. It is useful for applications where users have different levels of access to different parts of a page.
In some cases, you may need to use both route-level and component-level authorization to achieve the level of access control required for your application.
Ultimately, the choice between route-level and component-level authorization will depend on your specific application requirements and the level of control you need over access to your application resources.
To implement route-level authorization in your Blazor application, it is recommended that you have a good understanding of the previous tutorials. Follow these steps to set up route authorization:
RouteView
component with the AuthorizeRouteView
component in your App.razor file. Here is an example:<CascadingAuthenticationState> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> </AuthorizeRouteView> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> ... </NotFound> </Router> </CascadingAuthenticationState>
NotAuthorized
and Authorizing
projected content inside the AuthorizeRouteView
component. This will be displayed when the user is not authorized to access a resource or when the application is authorizing the user's access.<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"> <NotAuthorized> <div>You are not authorized.</div> </NotAuthorized> <Authorizing> <div>Authorizing...</div> </Authorizing> </AuthorizeRouteView>
@using Microsoft.AspNetCore.Authorization @attribute [Authorize]
When you create a new independent component, you need to decide whether anyone can access it or if it's only available to authenticated users. By default, any new independent component is only accessible to authenticated users. If you want unauthenticated users to access the component, you can add the @attribute [AllowAnonymous]
directive to the component's section.
To implement component-level authorization in your Blazor application, follow these steps:
AuthorizeView
component, like this:<AuthorizeView> <Authorized><div>Authenticated</div></Authorized> <Authorizing><div>Loading...</div></Authorizing> <NotAuthorized><div>Not authenticated</div></NotAuthorized> </AuthorizeView>
AuthorizeView
component, define the content that should be displayed for each scenario:Authorized
content will be displayed if the user is authorized to access the resource.Authorizing
content will be displayed while the application is determining the user's authorization status.NotAuthorized
content will be displayed if the user is not authorized to access the resource.By wrapping your content in the AuthorizeView
component and defining the appropriate content for each scenario, you can ensure that only authorized users are able to view or interact with the content within your component.
In a Blazor application, a resource such as a route, component, or button can be protected by an authorization rule. When a user meets all of the requirements of an authorization rule, they are granted access to the resource. There are two main types of authorization rules that can be used to control access in Blazor:
Role-based authorization is a common way to control access to resources in a Blazor application. With this approach, the resource is protected by a list of roles, and when a user belongs to one of the roles in the list, they are granted access to the resource.
A role is simply a string
that can be assigned to a user to define their access level within the application. However, to include the role in a ClaimsIdentity
, it must be assigned under the type ClaimTypes.Role
.
To implement role-based authentication in your Blazor application, you can follow these steps:
User
model, as a user may have multiple roles.public class User { ... public List<string> Roles { get; set; } = new(); }
ToClaimsPrincipal
method to convert the list of roles to the ClaimsPrincipal
.public class User { ... public ClaimsPrincipal ToClaimsPrincipal() => new (new ClaimsIdentity(new Claim[] { ... }.Concat(Roles.Select(r => new Claim(ClaimTypes.Role, r)).ToArray()), "BlazorSchool")); }
FromClaimsPrincipal
method to convert the ClaimsPrincipal
back to the User
model.public class User { ... public static User FromClaimsPrincipal(ClaimsPrincipal principal) => new() { ... Roles = principal.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList() }; }
Roles
parameter in the AuthorizeView
component. For example:<AuthorizeView Roles="normal_user,paid_user"> <Authorized> <div>Content for users with either normal or paid or both roles.</div> </Authorized> </AuthorizeView> <AuthorizeView Roles="paid_user"> <Authorized> <div>Content for paid users.</div> </Authorized> </AuthorizeView> <AuthorizeView Roles="admin"> <Authorized> <div>Content for admin users.</div> </Authorized> </AuthorizeView>
Alternatively, you can set the required roles for the entire component in the directive section. For example:
@attribute [Authorize(Roles ="admin,paid_user")] <div>This page is for admin or paid users only.</div>
Policy-based rules provide more flexibility in authorization. You can use built-in requirements or define your custom requirements to meet the specific needs of your application.
A policy specifies the requirements that a user must meet to access a resource. If a user fails to comply with any of the conditions in the policy, they will be denied access to the resource. For example, suppose your website only sells special alcohol to premium adult users. In that case, the policy would require that the user be older than 18 (the first requirement) and a premium user (the second requirement). Your website will refuse to sell special alcohol if the user fails to meet either of those requirements.
A policy has 2 building blocks:
The requirement provides the static data needed to authorize the user. A policy includes one or more requirements, which can be reused in multiple policies. For example, to consider a user an adult, they must be 18 or older. In this case, 18 is the static data used to authorize the user, and you can declare a property in the requirement to yield this value. 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 is responsible for checking if the user satisfies the rule or not. The requirement handler also provides dynamic data to authorize the user. For example, your website sells video games that have ESRB ratings, which require the user to be older than 5, 7, 8, 13, or 18 to play the game. The required age is a dynamic number based on the game, and it is passed to the requirement handler.
To implement policy-based authentication, follow these steps:
User
model to authorize the user. For example, if you want to authorize the user's age, add a new property Age
to the User
model:public class User { ... public int Age { get; set; } }
ToClaimsPrincipal
method to convert the list of roles to the ClaimsPrincipal
:public class User { ... public ClaimsPrincipal ToClaimsPrincipal() => new (new ClaimsIdentity(new Claim[] { .... new (nameof(Age), Age.ToString()) }, "BlazorSchool")); }
FromClaimsPrincipal
method to convert the ClaimsPrincipal
back to the user model:public class User { ... public static User FromClaimsPrincipal(ClaimsPrincipal principal) => new() { ... Age = Convert.ToInt32(principal.FindFirstValue(nameof(Age))) }; }
IAuthorizationRequirement
interface. For example:public class AdultRequirement : IAuthorizationRequirement { public int MinimumAgeToConsiderAnAdult { get; set; } = 18; }
AuthorizationHandler<T>
class where T
is the requirement class. For example:public class AdultRequirementHandler : AuthorizationHandler<AdultRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdultRequirement requirement) { var user = User.FromClaimsPrincipal(context.User); if (user.Age >= requirement.MinimumAgeToConsiderAnAdult) { context.Succeed(requirement); } else { context.Fail(); } return Task.CompletedTask; } }
Call the Succeed(requirement)
method to indicate that the user passes the requirement. Otherwise, call the Fail()
method.
builder.Services.AddScoped<IAuthorizationHandler, AdultRequirementHandler>();
builder.Services.AddAuthorizationCore(config => { config.AddPolicy("AdultOnly", policy => policy.AddRequirements(new AdultRequirement())); }
You can define multiple policies inside the AddAuthorizationCore
method. A policy can also have multiple requirements. For example:
builder.Services.AddAuthorizationCore(config => { ... config.AddPolicy("AdultAdminOnly", policy => { policy.AddRequirements(new AdultRequirement()); policy.RequireRole("admin"); }); });
<AuthorizeView Policy="AdultOnly"> <Authorized> <div>Content for users in adult policy.</div> </Authorized> <NotAuthorized> <div>This content is for adult.</div> </NotAuthorized> </AuthorizeView>
You can also set the policy for the entire component in the directive section. For example:
@attribute [Authorize(Policy = "AdultOnly")] <div>Content for users in adult policy.</div>
In some cases, authorization rules may need to be dynamically determined based on the resource being accessed. For example, in the context of a video game, the ESRB rating of a game could dictate the minimum age requirement for accessing certain content. In such cases, it is necessary to pass the resource metadata to the requirement handler. Here is how to do it:
public class EsrbRequirement : IAuthorizationRequirement { }
context.Resource
property to receive the resource metadata. For example:public class EsrbRequirementHandler : AuthorizationHandler<EsrbRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EsrbRequirement requirement) { var user = User.FromClaimsPrincipal(context.User); int minimumAge = Convert.ToInt32(context.Resource); if (user.Age >= minimumAge) { context.Succeed(requirement); } else { context.Fail(); } return Task.CompletedTask; } }
builder.Services.AddScoped<IAuthorizationHandler, EsrbRequirementHandler>(); builder.Services.AddAuthorizationCore(config => { config.AddPolicy("EsrbPolicy", policy => policy.AddRequirements(new EsrbRequirement())); });
<AuthorizeView Policy="EsrbPolicy" Resource="13"> <Authorized> <div>Content for users in esrb policy with age 13+.</div> </Authorized> <NotAuthorized> <div>This content is for 13+ user only.</div> </NotAuthorized> </AuthorizeView>
This section highlights some common mistakes that members of the Blazor School Discord Community have encountered.
FocusOnNavigate
component in the App.razorWhen implementing the route-level authorization approach, modifying your App.razor component is crucial. However, it's essential to retain the FocusOnNavigate
component while implementing this approach to avoid issues.
When implementing the route-level authorization approach, it's vital to add the [AllowAnonymous]
attribute to your login page. Failure to do so would make it impossible for users to access your website.