ConvergeERP.Shared.Authorization.Core
2.0.2
dotnet add package ConvergeERP.Shared.Authorization.Core --version 2.0.2
NuGet\Install-Package ConvergeERP.Shared.Authorization.Core -Version 2.0.2
<PackageReference Include="ConvergeERP.Shared.Authorization.Core" Version="2.0.2" />
<PackageVersion Include="ConvergeERP.Shared.Authorization.Core" Version="2.0.2" />
<PackageReference Include="ConvergeERP.Shared.Authorization.Core" />
paket add ConvergeERP.Shared.Authorization.Core --version 2.0.2
#r "nuget: ConvergeERP.Shared.Authorization.Core, 2.0.2"
#:package ConvergeERP.Shared.Authorization.Core@2.0.2
#addin nuget:?package=ConvergeERP.Shared.Authorization.Core&version=2.0.2
#tool nuget:?package=ConvergeERP.Shared.Authorization.Core&version=2.0.2
ConvergeERP.Shared.Authorization.Core
A comprehensive authorization infrastructure library for ConvergeERP microservices providing JWT authentication, endpoint policy authorization, entity-level CRUD permissions, and resource-level access control with row-level filtering capabilities.
Table of Contents
- What's New
- Registration
- Configuration
- Features
- Permission Excel Sheets
- Architecture Overview
- JWT Token Structure
- Testing
- Related Packages
What's New
Version 2.0
- Startup Permission Seeding: Permissions and entity schemas can now be automatically published to the Identity Service at application startup via the
seedPermissionsAtStartupoption. - Built-in MassTransit Entity Schema Publisher: The library now includes
MassTransitEntitySchemaPublisher- no need to implementIEntitySchemaPublisheryourself. - Health Check Integration:
AddAuthorizationCacheHealthCheckextension method allows you to register a health check that monitors authorization cache status. - Sequential Startup Publishing: Entity schemas are published first, then permissions, ensuring the Identity Service has schema information before receiving permission data.
- Simplified Registration:
permissionsExcelPathandentityAssembliesare now optional whenseedPermissionsAtStartupisfalse. - Keyed Redis Cache: Authorization cache uses a dedicated keyed
IDistributedCacheinstance without InstanceName prefix, ensuring consistent cache keys across all services.
Registration
Follow these steps to integrate the authorization library into your service.
Step 1: Install the Package
dotnet add package ConvergeERP.Shared.Authorization.Core
Dependencies (automatically included):
Microsoft.AspNetCore.Authentication.JwtBearerMicrosoft.Extensions.Caching.StackExchangeRedisClosedXML(for Excel import)MassTransitConvergeERP.Shared.Authorization.AbstractionsConvergeERP.Shared.Domain
Step 2: Configure appsettings.json
Add the following configuration sections to your appsettings.json:
{
"JwtSettings": {
"IssuerSigningKey": "your-base64-encoded-256-bit-secret-key",
"ValidIssuer": "https://auth.yourcompany.com",
"ValidAudience": "https://api.yourcompany.com"
},
"ConnectionStrings": {
"Redis": "localhost:6379"
}
}
Environment variables (Docker/Kubernetes):
JwtSettings__IssuerSigningKey=your-base64-encoded-key
ConnectionStrings__Redis=redis:6379
Step 3: Setup Permission Seeding (Optional)
If you want to seed permissions at startup, create an Excel file with your permissions configuration.
3.1 Create the Excel File
Create a folder (e.g., Permissions/) in your project and add a permissions.xlsx file with the following sheets:
| Sheet Name | Purpose |
|---|---|
| Roles | Define available roles |
| RolePermissions | Feature-level permissions per role |
| EntityPermissions | CRUD permissions per entity per role |
| PermissionPolicies | Map API endpoints to required permissions |
| AccessRules | Resource-level access rules |
| AccessRuleConditions | Row-level filtering conditions (optional) |
See Permission Excel Sheets for detailed sheet formats.
3.2 Include Excel File in Build Output
Add to your .csproj file:
<ItemGroup>
<None Update="Permissions\permissions.xlsx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
Step 4: Register Services in Program.cs
Option A: With Permission Seeding at Startup (Recommended for Development)
using ConvergeERP.Shared.Authorization.Extensions;
var builder = WebApplication.CreateBuilder(args);
// 1. Register the authorization cache (Redis-based)
// Uses ConnectionStrings:Redis from configuration
builder.Services.AddConvergeAuthorizationCache(builder.Configuration);
// 2. Register authorization services with permission seeding
builder.Services.AddConvergeAuthorization(
builder.Configuration,
serviceName: "InventoryService",
permissionsExcelPath: "Permissions/permissions.xlsx",
entityAssemblies: [typeof(Product).Assembly, typeof(Order).Assembly],
seedPermissionsAtStartup: true
);
var app = builder.Build();
// 3. Configure middleware pipeline
app.UseRouting();
app.UseConvergeAuthorization(); // Adds Authentication, CurrentUser, and Authorization middleware
app.MapControllers();
app.Run();
Option B: Without Permission Seeding (Production)
using ConvergeERP.Shared.Authorization.Extensions;
var builder = WebApplication.CreateBuilder(args);
// 1. Register the authorization cache (Redis-based)
builder.Services.AddConvergeAuthorizationCache(builder.Configuration);
// 2. Register authorization services without seeding
builder.Services.AddConvergeAuthorization(
builder.Configuration,
serviceName: "InventoryService"
);
var app = builder.Build();
// 3. Configure middleware pipeline
app.UseRouting();
app.UseConvergeAuthorization();
app.MapControllers();
app.Run();
Option C: Using Connection String Directly
using ConvergeERP.Shared.Authorization.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register cache with explicit connection string
builder.Services.AddConvergeAuthorizationCache("localhost:6379");
builder.Services.AddConvergeAuthorization(
builder.Configuration,
serviceName: "InventoryService"
);
Step 5: Configure MassTransit (Required for Event Publishing)
The library uses MassTransit for publishing entity schemas and permissions to the Identity Service.
Using ConvergeERP.Shared.EventBus.RabbitMQ Package (Recommended)
using ConvergeERP.Shared.Authorization.Extensions;
var builder = WebApplication.CreateBuilder(args);
// ... authorization registrations ...
// Add RabbitMQ with your DbContext
builder.Services.AddRabbitMQEventBus<YourDbContext>(
[typeof(Program).Assembly]
);
Configure MassTransit Manually
var builder = WebApplication.CreateBuilder(args);
// ... authorization registrations ...
builder.Services.AddMassTransit(x =>
{
// Add your consumers
x.AddConsumers(typeof(Program).Assembly);
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("rabbitmq://localhost", h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ConfigureEndpoints(context);
});
});
Step 6: Add Health Checks (Optional)
Register the authorization cache health check to monitor sync status:
builder.Services.AddHealthChecks()
.AddAuthorizationCacheHealthCheck(
serviceName: "InventoryService",
tags: ["ready"]);
// Map health check endpoint
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
Complete Registration Example
using ConvergeERP.Shared.Authorization.Extensions;
var builder = WebApplication.CreateBuilder(args);
// 1. Add Redis cache for authorization
builder.Services.AddConvergeAuthorizationCache(builder.Configuration);
// 2. Add authorization services with startup seeding
builder.Services.AddConvergeAuthorization(
builder.Configuration,
serviceName: "InventoryService",
permissionsExcelPath: "Permissions/permissions.xlsx",
entityAssemblies: [typeof(Product).Assembly],
seedPermissionsAtStartup: true
);
// 3. Add MassTransit for event publishing
builder.Services.AddRabbitMQEventBus<AppDbContext>(
[typeof(Program).Assembly]
);
// 4. Add health checks
builder.Services.AddHealthChecks()
.AddAuthorizationCacheHealthCheck("InventoryService", tags: ["ready"]);
var app = builder.Build();
// 5. Configure middleware
app.UseRouting();
app.UseConvergeAuthorization();
app.MapControllers();
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
app.Run();
Registered Services Summary
When you call AddConvergeAuthorization, the following services are registered:
| Service | Lifetime | Description |
|---|---|---|
ICurrentUser |
Scoped | Access current user context from JWT claims |
ICurrentUserSetter |
Scoped | Internal interface for setting user context |
IAccessRulesEvaluator |
Scoped | Evaluate resource-level access rules |
IEntityAccessEvaluator |
Scoped | Check entity CRUD permissions |
IEndpointPolicyStore |
Singleton | Store and lookup endpoint policies |
IAuthorizationCache |
Singleton | Redis-based authorization data cache |
IEntitySchemaPublisher |
Scoped | Publish entity schemas via MassTransit |
RolePermissionResolver |
Scoped | Resolve permissions from cache |
EndpointPolicyAuthorizationHandler |
Scoped | ASP.NET Core authorization handler |
When seedPermissionsAtStartup is true, an additional AuthorizationStartupService (hosted service) is registered to publish entity schemas and permissions at startup.
Configuration
JWT Settings
| Property | Description |
|---|---|
IssuerSigningKey |
Base64-encoded symmetric key for JWT signature validation |
ValidIssuer |
Expected iss claim in the JWT |
ValidAudience |
Expected aud claim in the JWT |
Authorization Cache (Redis)
The authorization cache is configured using the ConnectionStrings:Redis configuration key. The library registers a keyed IDistributedCache instance specifically for authorization with no InstanceName prefix, ensuring consistent cache keys across all services regardless of any consumer cache configuration.
{
"ConnectionStrings": {
"Redis": "localhost:6379"
}
}
Note: Your application can have its own IDistributedCache registration with any InstanceName prefix - the authorization cache will use its own dedicated instance.
Features
Current User Context
The ICurrentUser interface provides access to the authenticated user's context, populated from JWT claims and request headers.
public interface ICurrentUser
{
// User Identity
Guid? UserId { get; }
string Email { get; }
string Name { get; }
string Phone { get; }
bool IsTenantAdmin { get; }
// Multi-Tenant Context
Guid? TenantId { get; }
List<Guid> Companies { get; }
Guid? CompanyId { get; } // From Company_Id header
UserScope Scope { get; } // Global, Tenant, or Company
// Company-Scoped Permissions
Dictionary<Guid, List<string>> Roles { get; }
Dictionary<Guid, List<string>> Permissions { get; }
Dictionary<Guid, List<EntityAccess>> EntityAccess { get; }
Dictionary<Guid, List<ResourceAccess>> Resources { get; }
// Current Company Context (based on CompanyId header)
List<string> CurrentRoles { get; }
List<string> CurrentPermissions { get; }
List<EntityAccess> CurrentEntityAccess { get; }
List<ResourceAccess> CurrentResources { get; }
}
Usage
public class ProductService
{
private readonly ICurrentUser _currentUser;
public ProductService(ICurrentUser currentUser)
{
_currentUser = currentUser;
}
public async Task<List<Product>> GetProductsAsync()
{
// Check user scope
if (_currentUser.Scope == UserScope.Global)
{
return await GetAllGlobalProducts();
}
// Check permissions for current company
if (!_currentUser.CurrentPermissions.Contains("Inventory.Products.Read"))
{
throw new UnauthorizedAccessException();
}
// Check if user is tenant admin (bypasses access rules)
if (_currentUser.IsTenantAdmin)
{
return await GetAllCompanyProducts(_currentUser.CompanyId!.Value);
}
return await GetFilteredProducts(_currentUser.CompanyId!.Value);
}
}
Company Context Header
Set the current company context via the Company_Id HTTP header:
GET /api/products HTTP/1.1
Authorization: Bearer <jwt-token>
Company_Id: 550e8400-e29b-41d4-a716-446655440000
Endpoint Policy Authorization
Automatically enforced via ASP.NET Core authorization middleware when using [Authorize(Policy = "EndpointPolicy")].
Usage
[ApiController]
[Route("api/[controller]")]
[Authorize(Policy = "EndpointPolicy")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts() // Requires Inventory.Products.Read
{
// ...
}
[HttpPost]
public IActionResult CreateProduct() // Requires Inventory.Products.Create
{
// ...
}
}
How It Works
- Request comes in with method and path (e.g.,
GET /api/products) EndpointPolicyAuthorizationHandlerlooks up the policy fromIEndpointPolicyStore- Handler checks if user has any of the required permissions
- User's permissions come from
ICurrentUser.CurrentPermissions(company-scoped)
Entity Access Evaluator
Manual CRUD permission checks for entity types. Use when you need to check permissions before expensive operations.
Usage
public class ProductService
{
private readonly IEntityAccessEvaluator _entityAccess;
public ProductService(IEntityAccessEvaluator entityAccess)
{
_entityAccess = entityAccess;
}
public async Task CreateProductAsync(ProductDto dto)
{
// Check BEFORE calling expensive external API
if (!_entityAccess.CanCreate("Product"))
throw new UnauthorizedAccessException("Cannot create products");
await _externalApi.ValidateProduct(dto);
var product = MapToEntity(dto);
await _repository.AddAsync(product);
}
}
Interface
public interface IEntityAccessEvaluator
{
bool CanCreate(string entityName);
bool CanRead(string entityName);
bool CanUpdate(string entityName);
bool CanDelete(string entityName);
}
Access Rules Evaluator
Resource-level access with row-level filtering conditions. Useful for non-EF Core data access (Dapper, raw SQL).
Usage
public class ProductService
{
private readonly IAccessRulesEvaluator _accessRules;
public ProductService(IAccessRulesEvaluator accessRules)
{
_accessRules = accessRules;
}
public async Task<List<Product>> GetProductsWithDapperAsync()
{
if (!_accessRules.HasCompanyAccess("Product"))
return [];
var accessResult = _accessRules.GetAccessRulesForEntity("Product");
if (accessResult == null || accessResult.DenyAll)
return [];
var sql = "SELECT * FROM Products WHERE CompanyId = @CompanyId";
if (accessResult.Read.HasConditions)
{
sql += BuildConditionsSql(accessResult.Read.Conditions);
}
return await _connection.QueryAsync<Product>(sql, new { CompanyId = _currentUser.CompanyId });
}
}
Interface
public interface IAccessRulesEvaluator
{
bool HasCompanyAccess(string entity);
AccessEvaluationResult? GetAccessRulesForEntity(string entityName);
}
Entity Schema Publishing
Automatically scans your domain assemblies for entity types (classes inheriting from GlobalBaseEntity) and publishes their field schemas to the Identity Service at startup.
How It Works
- On Startup: The
AuthorizationStartupServicebackground service runs automatically - Scans Assemblies: Finds all entity types in the specified assemblies
- Extracts Fields: Collects property names and types
- Publishes Schema: Sends
EntitySchemaEventmessages via MassTransit
Example Published Schema
For an entity:
public class Product : BaseEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsActive { get; set; }
}
The published schema:
{
"entityType": "Product",
"sourceService": "InventoryService",
"fields": [
{ "name": "Id", "type": "guid" },
{ "name": "TenantId", "type": "guid" },
{ "name": "Name", "type": "string" },
{ "name": "Price", "type": "decimal" },
{ "name": "IsActive", "type": "bool" }
]
}
Manual Schema Scanning
var schemas = EntitySchemaScanner.ScanAssemblies(
assemblies: [typeof(Product).Assembly],
sourceService: "InventoryService"
);
Permission Excel Sheets
The library imports permissions from an Excel file with multiple sheets. This file defines roles, feature permissions, entity permissions, endpoint policies, and access rules.
Required Excel Sheets
1. Roles Sheet
Defines available roles in the system.
| Column | Description |
|---|---|
| A: Name | Role name (e.g., "Admin", "Manager", "Viewer") |
| B: Description | Role description |
Example:
| Name | Description |
|---|---|
| Admin | Full system administrator |
| Manager | Department manager with elevated access |
| Viewer | Read-only access |
2. RolePermissions Sheet
Defines feature-level permissions assigned to roles. Permissions follow the pattern: {Module}.{Feature}.{Action}.
| Column | Description |
|---|---|
| A: RoleName | Role this permission applies to |
| B: Module | Module name (e.g., "Inventory", "Sales") |
| C: Feature | Feature within the module (e.g., "Products", "Orders") |
| D: Action | Action name (e.g., "Create", "Read", "Update", "Delete") |
| E: Description | Optional description |
Example:
| RoleName | Module | Feature | Action | Description |
|---|---|---|---|---|
| Admin | Inventory | Products | Create | Create new products |
| Admin | Inventory | Products | Read | View products |
| Admin | Inventory | Products | Update | Modify products |
| Admin | Inventory | Products | Delete | Remove products |
| Manager | Inventory | Products | Create | Create new products |
| Manager | Inventory | Products | Read | View products |
| Viewer | Inventory | Products | Read | View products |
This generates permissions like: Inventory.Products.Create, Inventory.Products.Read, etc.
3. EntityPermissions Sheet
Defines entity-level CRUD permissions for roles. Used by IEntityAccessEvaluator.
| Column | Description |
|---|---|
| A: Role | Role name |
| B: Entity | Entity type name (e.g., "Product", "Order") |
| C: Create | TRUE/FALSE |
| D: Read | TRUE/FALSE |
| E: Update | TRUE/FALSE |
| F: Delete | TRUE/FALSE |
Example:
| Role | Entity | Create | Read | Update | Delete |
|---|---|---|---|---|---|
| Admin | Product | TRUE | TRUE | TRUE | TRUE |
| Manager | Product | TRUE | TRUE | TRUE | FALSE |
| Viewer | Product | FALSE | TRUE | FALSE | FALSE |
4. PermissionPolicies Sheet
Maps API endpoints to required permissions. Used by EndpointPolicyAuthorizationHandler.
| Column | Description |
|---|---|
| A: Endpoint | HTTP method and path (e.g., "GET /api/products") |
| B: Permissions | Comma-separated list of required permissions |
| C: RequiresAuthorization | TRUE/FALSE - whether authentication is required |
Example:
| Endpoint | Permissions | RequiresAuthorization |
|---|---|---|
| GET /api/products | Inventory.Products.Read | TRUE |
| POST /api/products | Inventory.Products.Create | TRUE |
| PUT /api/products/{id} | Inventory.Products.Update | TRUE |
| DELETE /api/products/{id} | Inventory.Products.Delete | TRUE |
| GET /api/health | FALSE |
Route Parameters: Use {paramName} syntax for route parameters. The library handles pattern matching automatically.
5. AccessRules Sheet
Defines resource-level access rules with optional conditions. Used by IAccessRulesEvaluator.
| Column | Description |
|---|---|
| A: Name | Unique rule name |
| B: ScopeType | "Global" or "RoleBased" |
| C: RoleName | Role name (only for RoleBased scope) |
| D: EntityName | Entity type this rule applies to |
| E: Create | TRUE/FALSE |
| F: Read | TRUE/FALSE |
| G: Update | TRUE/FALSE |
| H: Delete | TRUE/FALSE |
Example:
| Name | ScopeType | RoleName | EntityName | Create | Read | Update | Delete |
|---|---|---|---|---|---|---|---|
| AdminProductAccess | RoleBased | Admin | Product | TRUE | TRUE | TRUE | TRUE |
| ManagerProductAccess | RoleBased | Manager | Product | TRUE | TRUE | TRUE | FALSE |
| ViewerProductAccess | RoleBased | Viewer | Product | FALSE | TRUE | FALSE | FALSE |
6. AccessRuleConditions Sheet (Optional)
Defines row-level filtering conditions for access rules. Links to rules in the AccessRules sheet.
| Column | Description |
|---|---|
| A: RuleName | References the Name column in AccessRules |
| B: Left | Left operand (e.g., "resource.TenantId", "resource.CreatedBy") |
| C: Operator | Comparison operator: "Equals", "NotEquals", "Contains", "In" |
| D: Right | Right operand (e.g., "user.TenantId", "user.Id") |
Example:
| RuleName | Left | Operator | Right |
|---|---|---|---|
| ManagerProductAccess | resource.TenantId | Equals | user.TenantId |
| ManagerProductAccess | resource.CreatedBy | Equals | user.Id |
Complete Excel Template Structure
permissions.xlsx
??? Roles
??? RolePermissions
??? EntityPermissions
??? PermissionPolicies
??? AccessRules
??? AccessRuleConditions (optional)
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- ClosedXML (>= 0.105.0)
- ConvergeERP.Shared.Authorization.Abstractions (>= 2.0.1)
- ConvergeERP.Shared.Domain (>= 2.0.5)
- MassTransit (>= 8.5.8)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.1)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 10.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.2 | 303 | 3/19/2026 |
| 2.0.1 | 1,215 | 3/10/2026 |
| 2.0.0 | 254 | 3/7/2026 |
| 1.0.6 | 262 | 2/25/2026 |
| 1.0.5 | 110 | 2/24/2026 |
| 1.0.4 | 108 | 2/24/2026 |
| 1.0.3 | 115 | 2/11/2026 |
| 1.0.2 | 203 | 1/25/2026 |
| 0.0.13-beta | 229 | 6/7/2026 |
| 0.0.12-beta | 99 | 6/6/2026 |
| 0.0.11-beta | 310 | 5/20/2026 |
| 0.0.10-beta | 89 | 5/20/2026 |
| 0.0.9-beta | 200 | 5/18/2026 |
| 0.0.8-beta | 97 | 5/18/2026 |
| 0.0.7-beta | 232 | 5/8/2026 |
| 0.0.6-beta | 308 | 4/21/2026 |
| 0.0.5-beta | 106 | 4/20/2026 |
| 0.0.4-beta | 401 | 4/11/2026 |
| 0.0.3-beta | 739 | 4/9/2026 |
| 0.0.1-beta | 116 | 3/28/2026 |