RBAC Integration Guide (.NET)
Follow the five-step integration guide below. Choose the storage adapter that fits your environment.
Step 1: Install the package
In-Memory adapter (development and testing):
dotnet add package PrimusSaaS.Rbac
dotnet add package PrimusSaaS.Rbac.InMemory
EF Core adapter (production database):
dotnet add package PrimusSaaS.Rbac
dotnet add package PrimusSaaS.Rbac.EFCore
Step 2: Configure Program.cs
In-Memory adapter:
using PrimusSaaS.Rbac;
using PrimusSaaS.Rbac.InMemory;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPrimusRbacInMemory(options =>
builder.Configuration.GetSection("Rbac").Bind(options));
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Optional: bridge Multi-Tenancy tenant context into RBAC
If your app uses PrimusSaaS.MultiTenancy, register the bridge so RBAC automatically resolves
tenant scope from ambient tenant context when RbacAccessRequest.TenantId is omitted.
using PrimusSaaS.MultiTenancy.Composition;
builder.Services.AddTenantRbacBridge();
This registers IRbacTenantIdProvider backed by ITenantContextAccessor.
EF Core adapter:
Replace AppDbContext with your application's DbContext.
using Microsoft.EntityFrameworkCore;
using PrimusSaaS.Rbac;
using PrimusSaaS.Rbac.EFCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddPrimusRbacEfCore<AppDbContext>(options =>
builder.Configuration.GetSection("Rbac").Bind(options));
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Update your DbContext model configuration:
using PrimusSaaS.Rbac.EFCore;
public class AppDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.AddPrimusRbac();
}
}
Seeding roles and permissions at startup
Use the SeedPrimusRbac helper to seed initial data safely after the app is built. It creates a scoped DI scope internally so it works with both adapters.
var app = builder.Build();
app.Services.SeedPrimusRbac(rbac =>
{
rbac.UpsertPermission(new RbacPermission(
Id: "perm:reports:read",
Action: "read",
Resource: "reports",
Effect: RbacEffect.Allow,
Scope: new RbacScope()));
rbac.UpsertRole(new RbacRole(
Id: "role:viewer",
Name: "Viewer",
Scope: new RbacScope(),
PermissionIds: new[] { "perm:reports:read" }));
});
app.MapControllers();
app.Run();
SeedPrimusRbacis idempotent — calling it multiple times on the same IDs is safe becauseUpsertPermission/UpsertRoleoverwrite existing entries.
Step 3: Configure appsettings.json
In-Memory adapter:
{
"Rbac": {
"SeedMode": "none",
"AllowMultipleInheritance": false
}
}
EF Core adapter:
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=primus_rbac;Trusted_Connection=True;"
},
"Rbac": {
"SeedMode": "none",
"AllowMultipleInheritance": false
}
}
Seed modes:
none: no preloaded roles or permissions.minimal: adds a singlerole:administratorwithperm:all.sample: adds example roles, permissions, and groups.
How to get configuration values
ConnectionStrings:DefaultConnectionis your standard database connection string (SQL Server, PostgreSQL, or SQLite).Rbac:SeedModecontrols optional starter data (none,minimal,sample).Rbac:AllowMultipleInheritanceallows more than one parent role. Keepfalsefor a strict tree.
Optional: If you want to select the adapter via configuration, add Rbac:Store and branch in Program.cs. Use database for EF Core or in-memory for the In-Memory adapter.
Step 4: Configure endpoints
Expose RBAC endpoints using IRbacService. The access check endpoint is required. The admin UI expects the role, permission, group, membership, assignment, snapshot, and reset endpoints shown below.
using Microsoft.AspNetCore.Mvc;
using PrimusSaaS.Rbac;
[ApiController]
[Route("api/rbac")]
public class RbacController : ControllerBase
{
private readonly IRbacService _rbac;
public RbacController(IRbacService rbac) => _rbac = rbac;
[HttpPost("check")]
public IActionResult Check([FromBody] RbacAccessRequest request)
{
var decision = _rbac.Evaluate(request);
return Ok(decision);
}
[HttpGet("roles")]
public IActionResult ListRoles([FromQuery] string? applicationId = null, [FromQuery] string? tenantId = null)
=> Ok(_rbac.ListRoles(applicationId, tenantId));
[HttpPost("roles")]
public IActionResult UpsertRole([FromBody] RbacRole role)
{
_rbac.UpsertRole(role);
return Ok(role);
}
[HttpDelete("roles/{roleId}")]
public IActionResult DeleteRole(string roleId)
{
_rbac.DeleteRole(roleId);
return Ok();
}
[HttpGet("permissions")]
public IActionResult ListPermissions([FromQuery] string? applicationId = null, [FromQuery] string? tenantId = null)
=> Ok(_rbac.ListPermissions(applicationId, tenantId));
[HttpPost("permissions")]
public IActionResult UpsertPermission([FromBody] RbacPermission permission)
{
_rbac.UpsertPermission(permission);
return Ok(permission);
}
[HttpDelete("permissions/{permissionId}")]
public IActionResult DeletePermission(string permissionId)
{
_rbac.DeletePermission(permissionId);
return Ok();
}
[HttpGet("groups")]
public IActionResult ListGroups([FromQuery] string? applicationId = null, [FromQuery] string? tenantId = null)
=> Ok(_rbac.ListGroups(applicationId, tenantId));
[HttpPost("groups")]
public IActionResult UpsertGroup([FromBody] RbacGroup group)
{
_rbac.UpsertGroup(group);
return Ok(group);
}
[HttpDelete("groups/{groupId}")]
public IActionResult DeleteGroup(string groupId)
{
_rbac.DeleteGroup(groupId);
return Ok();
}
[HttpGet("memberships")]
public IActionResult ListMemberships([FromQuery] string? principalId = null, [FromQuery] string? groupId = null)
=> Ok(_rbac.ListMemberships(principalId, groupId));
[HttpPost("memberships")]
public IActionResult UpsertMembership([FromBody] RbacMembership membership)
{
_rbac.UpsertMembership(membership);
return Ok(membership);
}
[HttpDelete("memberships/{membershipId}")]
public IActionResult DeleteMembership(string membershipId)
{
_rbac.DeleteMembership(membershipId);
return Ok();
}
[HttpGet("assignments/{principalId}")]
public IActionResult ListAssignments(string principalId)
=> Ok(_rbac.ListAssignments(principalId));
[HttpPost("assignments")]
public IActionResult UpsertAssignment([FromBody] RbacAssignment assignment)
{
_rbac.UpsertAssignment(assignment);
return Ok(assignment);
}
[HttpDelete("assignments/{assignmentId}")]
public IActionResult DeleteAssignment(string assignmentId)
{
_rbac.DeleteAssignment(assignmentId);
return Ok();
}
[HttpGet("snapshot")]
public IActionResult Snapshot() => Ok(_rbac.Snapshot());
[HttpPost("reset")]
public IActionResult Reset()
{
_rbac.Reset();
return Ok();
}
}
Secure these endpoints with your authentication layer before exposing them to the UI.
Optional: bulk administration endpoints
For high-volume admin workflows, expose RBAC bulk operations:
BulkUpsertRoles(IEnumerable<RbacRole>)BulkUpsertPermissions(IEnumerable<RbacPermission>)BulkUpsertAssignments(IEnumerable<RbacAssignment>)BulkDeleteRoles(IEnumerable<string>)BulkDeletePermissions(IEnumerable<string>)BulkDeleteAssignments(IEnumerable<string>)
These methods are synchronous and intended for import jobs and admin batch updates.
Step 5: Test the endpoint
curl -X POST http://localhost:5000/api/rbac/check \
-H "Content-Type: application/json" \
-d '{
"principalId": "user-123",
"principalType": "user",
"roles": ["Builder"],
"action": "write",
"resource": "resource:main",
"applicationId": "app-sales",
"tenantId": "tenant-001",
"attributes": { "status": "active", "region": "us", "amount": 1200 }
}'
You should receive an RbacDecision response with allowed, reason, and matched role/permission arrays.
Example allow response:
{
"allowed": true,
"reason": "allow_rule_matched",
"matchedPermissions": ["perm:resource:write"],
"matchedRoles": ["role:builder"],
"denyRules": [],
"context": {
"scope_key": "app-sales|tenant-001|*",
"scope_chain_length": 2,
"token_roles_count": 1,
"resolved_groups": 0,
"evaluated_permissions": 4
}
}
The context object provides decision trace details for diagnostics and observability.
Common reason values:
| Value | Meaning |
|---|---|
allow_rule_matched | A permission with Allow effect matched |
deny_rule_matched | A permission with Deny effect matched — denies override allows |
no_match | No role/permission combination matched the request |
See the Advanced guide for audit logging and condition operators.