A framework to easily add authentication to a Blazor server application.
- Set up your user repository/database. Create a user type that implements
BlazorEasyAuth.Models.IUser
- Create your roles
Creating roles is easy. The recommended way is to create a static classstatic class Roles
and createpublic static readonly Role
properties with the roles. Ex.:Thepublic static class Roles { public static readonly Role Superuser = new(1000); public static readonly Role Administrator = new(999); public static readonly Role ManageUsers = new(); public static readonly Role MyRole = new(); }
Role
class uses the name of the caller to determine the name of the role. In the above example, we're creating roles with the namesSuperuser, Administrator, ManageUsers, MyRole
. Another parameter ispriority
. This is an integer value that can be used to determine if a certain role is "above" another, for policy evaluation later on. (ex. a Superuser can manage administrators, but not the other way around, therefore the "Priority" is higher on the Superuser role).
You can also instantiate roles at runtime at any point simply by constructing them. - Create policies
Similar to roles, creating policies is very easy and is recommended to be done as static declarations. As there's often multiple policies per "resource" or area (like View/Create/Edit/Delete), policies will try to walk up the stack trace and use the declaring property's full name to create the policy. Ex.:This example will create policies with names likepublic static class Policies { public static class Users { public static readonly Policy View = new(p => p.RequireRole(Roles.ManageUsers, Roles.Administrator, Roles.Superuser)); public static readonly Policy Edit = new(p => p .RequireRole(Roles.ManageUsers, Roles.Administrator, Roles.Superuser) .AddRelativeRoleRequirement(RoleRequirement.Lesser)); public static readonly Policy Delete = new(p => p .RequireRole(Roles.ManageUsers, Roles.Administrator, Roles.Superuser) .AddRelativeRoleRequirement(RoleRequirement.Lesser)); } public static class MyResource { public static readonly Policy View = new(p => p.RequireRole(Roles.MyRole, Roles.Administrator, Roles.Superuser)); public static readonly Policy Edit = new(p => p.RequireRole(Roles.MyRole, Roles.Administrator, Roles.Superuser)); public static readonly Policy Create = new(p => p.RequireRole(Roles.MyRole, Roles.Administrator, Roles.Superuser)); } }
BlazorEasyAuth.Example.Models.Policies.Users.View/Edit/Delete
. You also have to specify the policy builder immediately.
If you want to create policies at runtime, you have to instantiate them before callingservices.AddBlazorEasyAuth<>()
- Create a
NotAuthorizedPage.razor
page (see example in this project) - Modify
App.razor
- Wrap
<Router>
in<CascadingAuthenticationState>
tags - In
<Router><Found>
, replace<RouteView>
with<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"> <NotAuthorized> <NotAuthorizedPage /> </NotAuthorized> </AuthorizeRouteView>
- Wrap
- Create a
BlazorEasyAuth.Providers.Interfaces.IUserProvider
implementation (MyUserProviderImplementation
used as example in next step). - Modify
Startup.cs
- Add services to
ConfigureServices(IServiceCollection services)
Specify the type created in the previous step that implementsIUserProvider
as type argumentservices.AddBlazorEasyAuth<MyUserProviderImplementation>();
- In
void Configure(IApplicationBuilder app, IWebHostEnvironment env)
afterapp.UseRouting();
, add:app.UseCookiePolicy(); app.UseAuthentication();
- Inside
app.UseEndpoints(...)
, make sure there's aendpoints.MapControllers();
callapp.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); });
- Add services to
- Create a sign in page at the URL
@page "/authentication/signin/{ReturnUrl?}"
. SeeSignIn.razor
as example.
The Role and Policy classes have some extra helper functions. For example, the Role class has several comparison operators that allow you to compare the priority of two roles to one another.
var superuser = new Role(100);
var administrator = new Role(50);
var equalToAdministratorPrio = new Role(50);
superuser > administrator
// true
administrator < superuser
// true
superuser <= administrator
// false
administrator >= superuser
// false
// Equality operator doesn't check the relative priority, but actually checks if the role is equal
administrator == equalToAdministratorPrio
// false
// <= and >= will in fact check relative priorities
administrator <= equalToAdministratorPrio
// true
administrator >= equalToAdministratorPrio
// true
Roles are unique by the name, and equality comparison happens based on that name. If you instantiate 2 roles in 2 different classes/properties with the same name, they will equal each other:
class Class1 {
public static readonly Role MyRole = new Role();
}
class Class2 {
public static readonly Role MyRole = new Role();
}
Class1.MyRole == Class2.MyRole
// true
Roles and policies also have an implicit string operator, allowing you to use it in methods that accept a string, or comparing a string to a role:
public static readonly Role MyRole = new Role();
// in file My/Name/Space/Policies.cs
public static readonly Policy MyPolicy = new Policy(...);
MyRole == "MyRole"
// true
MyRole == "SomethingElse"
// false
MyPolicy == "My.Name.Space.Policies.MyPolicy"
// true
MyPolicy == "Something.Else"
// false
Because roles and policies have implicit string operators, it is easy to use them in, for example, <AuthorizeView>
tags:
<AuthorizeView Policy="@Policies.User.Create" Roles="@string.Join(',', Roles.ManageUsers, Roles.Administrator, Roles.Superuser)">
...
</AuthorizeView>
If you need to enumerate all available roles, there's a property on the Role
class that allows you to do this: Role.AllRoles
Similarly for policies: Policy.AllPolicies