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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ConvergeERP.Shared.Authorization.Core" Version="2.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ConvergeERP.Shared.Authorization.Core" Version="2.0.2" />
                    
Directory.Packages.props
<PackageReference Include="ConvergeERP.Shared.Authorization.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ConvergeERP.Shared.Authorization.Core --version 2.0.2
                    
#r "nuget: ConvergeERP.Shared.Authorization.Core, 2.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ConvergeERP.Shared.Authorization.Core@2.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ConvergeERP.Shared.Authorization.Core&version=2.0.2
                    
Install as a Cake Addin
#tool nuget:?package=ConvergeERP.Shared.Authorization.Core&version=2.0.2
                    
Install as a Cake Tool

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

Version 2.0

  • Startup Permission Seeding: Permissions and entity schemas can now be automatically published to the Identity Service at application startup via the seedPermissionsAtStartup option.
  • Built-in MassTransit Entity Schema Publisher: The library now includes MassTransitEntitySchemaPublisher - no need to implement IEntitySchemaPublisher yourself.
  • Health Check Integration: AddAuthorizationCacheHealthCheck extension 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: permissionsExcelPath and entityAssemblies are now optional when seedPermissionsAtStartup is false.
  • Keyed Redis Cache: Authorization cache uses a dedicated keyed IDistributedCache instance 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.JwtBearer
  • Microsoft.Extensions.Caching.StackExchangeRedis
  • ClosedXML (for Excel import)
  • MassTransit
  • ConvergeERP.Shared.Authorization.Abstractions
  • ConvergeERP.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

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.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
  1. Request comes in with method and path (e.g., GET /api/products)
  2. EndpointPolicyAuthorizationHandler looks up the policy from IEndpointPolicyStore
  3. Handler checks if user has any of the required permissions
  4. 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
  1. On Startup: The AuthorizationStartupService background service runs automatically
  2. Scans Assemblies: Finds all entity types in the specified assemblies
  3. Extracts Fields: Collects property names and types
  4. Publishes Schema: Sends EntitySchemaEvent messages 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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