A .NET library for enforcing access control policies in Entity Framework Core applications with dependency injection support.
- π Declarative Access Control: Define policies at the entity level using a fluent API
- 𧩠Multiple Policy Types: Support for role-based, permission-based, ownership-based, and property-based policies
- β‘ Automatic Enforcement: Policies applied automatically to both queries and commands
- π Extensible: Build custom policy types for specific business requirements
- ποΈ Clean Architecture Friendly: Designed to work well with domain-driven and clean architecture approaches
- π§ͺ Testable: Easy to mock and test in isolation
- π Chainable Policies: Combine multiple policies with AND/OR operators
- π§ Smart Caching: Performance optimized with compiled expression caching
dotnet add package EntityFrameworkCore.PolicyEnforcement
public class ApplicationDbContext : DbContext
{
public DbSet<Document> Documents { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString)
.UsePolicyEnforcement();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Document>()
.HasAccessPolicy(policy =>
policy.RequireOwnership(d => d.OwnerId)
.Or(policy.RequireRole("Admin"))
);
}
}
Create a class implementing IUserContext
:
public class CurrentUserContext : IUserContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentUserContext(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string? GetCurrentUserId() =>
_httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
public bool IsInRole(string role) =>
_httpContextAccessor.HttpContext?.User?.IsInRole(role) ?? false;
public bool HasPermission(string permission) =>
_httpContextAccessor.HttpContext?.User?.HasClaim(c =>
c.Type == "Permission" && c.Value == permission) ?? false;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IUserContext, CurrentUserContext>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
.UsePolicyEnforcement(opt =>
{
opt.EnableForQueries = true;
opt.EnableForCommands = true;
opt.ThrowOnViolation = true;
}));
}
public class DocumentService
{
private readonly ApplicationDbContext _dbContext;
private readonly IUserContext _userContext;
public DocumentService(ApplicationDbContext dbContext, IUserContext userContext)
{
_dbContext = dbContext;
_userContext = userContext;
}
public async Task<List<Document>> GetUserDocumentsAsync()
{
// Set the user context before querying
_dbContext.SetUserContext(_userContext);
// Policy is automatically applied
return await _dbContext.Documents.ToListAsync();
}
}
modelBuilder.Entity<Payroll>()
.HasAccessPolicy(policy => policy.RequireRole("HR"));
modelBuilder.Entity<Customer>()
.HasAccessPolicy(policy => policy.RequirePermission("customers.read"));
modelBuilder.Entity<UserProfile>()
.HasAccessPolicy(policy => policy.RequireOwnership(p => p.UserId));
modelBuilder.Entity<Document>()
.HasAccessPolicy(policy => policy.RequireProperty(d => d.IsPublic));
modelBuilder.Entity<Project>()
.HasAccessPolicy(policy =>
policy.RequireOwnership(p => p.OwnerId)
.Or(policy.RequireRole("Manager"))
.Or(policy.RequireProperty(p => p.IsPublic))
);
modelBuilder.Entity<Ticket>()
.HasAccessPolicy(policy => policy.RequireRole("Support"), "Default")
.HasAccessPolicy(policy => policy.RequireRole("SupportLead"), "Update")
.HasAccessPolicy(policy => policy.RequireRole("Manager"), "Delete");
modelBuilder.Entity<FinancialRecord>()
.HasAccessPolicy(policy => policy.Custom(userContext =>
fr => fr.Amount < 1000 || userContext.IsInRole("FinancialApprover")
));
public class Team : IDefineOwnAccessPolicy
{
public int Id { get; set; }
public string Name { get; set; }
public List<TeamMember> Members { get; set; }
public bool CanAccess(IUserContext userContext, string operation)
{
var userId = userContext.GetCurrentUserId();
if (string.IsNullOrEmpty(userId)) return false;
// Allow members to read, but only admins to modify
if (operation == "Read")
return Members.Any(m => m.UserId == userId);
return Members.Any(m => m.UserId == userId && m.IsAdmin);
}
}
This project is licensed under the MIT License. See the LICENSE file for details.
π― Found a bug or have an idea for improvement?
Feel free to open an issue or submit a pull request!
π GitHub Issues
If you find this package useful, give it a star β on GitHub and share it with others! π