A libary that provides permission-based authorization.
The ASP.NET Core role-based authorization in combination with custom authorization policies is a good starting point for restricting users' access in an application, but it is very static and changes to the meaning of a role or a policy forces you to perform changes in your code and to re-deploy your application. This library aims to overcome this limitation.
To be able to dynamically change the access of a user we extend the role in a way that each role is made up of several fine gained permissions. Throughout the documentation and the sample applications we will use the following roles and permissions.
Role | Invoice.Read | Invoice.Write | Invoice.Delete | Invoice.Send | Invoice.Payment |
---|---|---|---|---|---|
Boss | YES | NO | NO | NO | NO |
Manager | YES | NO | YES | NO | NO |
Employee | YES | YES | NO | YES | YES |
In this fictional company the boss can only read invoices, the manager can read and delete invoices and the employees can read, write, send invocies and can trigger the selllement of the invoice.
If we build the authentication around the three role we will hard-code the access permissions in our
codebase f.e. using the [Authorize]
attribute. But if the boss decides he also wants
to be able to delete invoices, we need to change it in the source code.
Using permissions of the role defined in the table above in the [Authorize]
attribute instead
of the roles, we can just change the role configuration in a data store and assign the permision
Invoices.Delete to the role Boss. The boss used is then able to delete invocies without
the need to change the code and re-deploy the application.
The library consists of two parts:
- The base definitions, policies and services to be able to check an authenticated users claims
(i.e.
ClaimsPrincipal
) for asigned permissions. - An implementation of the permissions API to work with ASP.NET Core Identity.
It is possible to add different storages and claims providers. A library that wanst to provide
the permissions claims just needs to implement the IClaimsProvider
interface and the storage
mechanism of course.
In addition to the basic permissions of users, this library provides an optional multi-tenant feature. This feature allows assign tenants to users. The tenant infosmations are then added to the user's claims. Storage systems can then leverage the tenant information to alter queries (Single Database with Tenant Column) or to select connection strings (Tenant per Database).
A tenant may have several roles and the permissions of those roles are added to the user's permission claims. In that way additional permissions can be added to individual claims. The tenant sample applications provide tenants with a distict tenant role assigned which provide the following additional tenant permissions. Each tenant represents a separate company. The roles in this example represent different plans of a SaaS application.
Role | Invoice.Statistics | Invoice.TaxExport |
---|---|---|
Free | NO | NO |
Basic | YES | NO |
Professional | YES | YES |
Using tenants, roles and permissions is a good way to define differnent sets of features, f.e. when creating different plans of a SaaS application.
To configure the permissions with ASP.NET Identity and the default identity models add the following code to your application startup code. The example uses EF Core and SQLite to store the Identity models.
The users, roles and permissions are added to the storage using the ApplicationDbContext
and EF
Core migrations. The code is omitted in this document, but you can look it up in the samples code.
// Previous service configuration omitted.
builder.Services.AddControllers();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization();
builder.Services.AddPermissionsAuthorization();
builder.Services
.AddAuthentication(IdentityConstants.ApplicationScheme)
.AddIdentityCookies();
builder.Services
.AddDbContext<InvoicesContext>(options =>
{
options.UseSqlite("Filename=permissions.db");
})
.AddPermissionsIdentityCore<IdentityUser, IdentityRole, IdentityPermission>()
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddPermissionClaimsProvider()
.AddUserManager<AspNetUserManager<IdentityUser>>()
.AddRoleManager<AspNetRoleManager<IdentityRole>>()
.AddPermissionManager<AspNetPermissionManager<IdentityPermission>>()
.AddPermissionsEntityFrameworkStores<InvoicesContext>();
// Additional service configuration omitted.
There are several ways to rescript access in your application.
- Use the
[RequirePermission]
attribute to restrict access to controller actions. - Use the
HasPermission()
extension method with aClaimsPrincipal
instance. - Use the
HasPermission()
method of aIUserPermissionsService
instance. - Use the
RequirePermission
extension methods for Minimal API endpoints.
To retrict the access to an action methods just add the [Authorize]
attribute with the permission
name as contraint.
// ASP.NET MVC controller action with attribute.
[HttpGet]
[HasPermission("Invoice.Payment")]
public IActionResult Get()
{
return this.Ok();
}
// Razor Pages with attribute.
[HasPermission("Invoices.Read")]
public class InvoicesReadModel : PageModel
{
public void OnGet()
{
}
}
// Razor Pages with extension method.
public class InvoicesReadModel : PageModel
{
public IActionResult OnGet()
{
if(!this.User.HasPermission("Invoice.Read"))
{
if(this.User.IsAuthenticated())
{
return this.Forbid();
}
return this.Challenge();
}
return this.Page();
}
}
// Minimal API with extension method.
app.MapGet("invoices/statistics", (HttpContext context) =>
{
return Results.Ok();
})
.RequirePermission("Invoice.Statistics");
// Previous service configuration omitted.
builder.Services.AddControllers();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization();
builder.Services.AddPermissionsAuthorization();
builder.Services
.AddAuthentication(IdentityConstants.ApplicationScheme)
.AddIdentityCookies();
builder.Services
.AddDbContext<InvoicesContext>(options =>
{
options.UseSqlite("Filename=permissions.db");
})
.AddPermissionsIdentityCore<IdentityTenant, IdentityUser, IdentityRole, IdentityPermission>()
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddIdentityClaimsProvider()
.AddDefaultTenantProvider()
.AddTenantManager<AspNetTenantManager<IdentityTenant>>()
.AddUserManager<AspNetTenantUserManager<IdentityTenantUser>>()
.AddRoleManager<AspNetRoleManager<IdentityRole>>()
.AddPermissionManager<AspNetPermissionManager<IdentityPermission>>()
.AddPermissionsEntityFrameworkStores<InvoicesContext>();
// Additional service configuration omitted.