DKNet.EfCore.DataAuthorization
12.0.0
dotnet add package DKNet.EfCore.DataAuthorization --version 12.0.0
NuGet\Install-Package DKNet.EfCore.DataAuthorization -Version 12.0.0
<PackageReference Include="DKNet.EfCore.DataAuthorization" Version="12.0.0" />
<PackageVersion Include="DKNet.EfCore.DataAuthorization" Version="12.0.0" />
<PackageReference Include="DKNet.EfCore.DataAuthorization" />
paket add DKNet.EfCore.DataAuthorization --version 12.0.0
#r "nuget: DKNet.EfCore.DataAuthorization, 12.0.0"
#:package DKNet.EfCore.DataAuthorization@12.0.0
#addin nuget:?package=DKNet.EfCore.DataAuthorization&version=12.0.0
#tool nuget:?package=DKNet.EfCore.DataAuthorization&version=12.0.0
DKNet.EfCore.DataAuthorization
Row-level, ownership-based data authorization for EF Core: an automatic global query filter plus a SaveChanges
hook, so multi-tenant or per-user data isolation is enforced by the persistence layer instead of by convention in
every query.
Install
dotnet add package DKNet.EfCore.DataAuthorization
Features
IOwnedBy— marker interface entities implement to opt into ownership-based filtering and stamping.- Automatic global query filter — every
IOwnedByentity is scoped toIDataOwnerDbContext.AccessibleKeysautomatically; deny-by-default when empty, with an explicitIsUnrestrictedAccessescape hatch for admin/system contexts. Not bypassable via specificationIsIgnoreQueryFilters. - Automatic ownership stamping — a
SaveChangeshook stamps the current owner key onto newly addedIOwnedByentities (andCreatedBy/CreatedOnwhen the entity is also audited), and reverts any attempt to silently reassign an existing row's owner to a key the caller can't access. - Composes with signed-in-user auditing — when an
ICurrentUserProviderfromDKNet.EfCore.AuditLogssupplies a non-empty user for the save, this package stampsOwnedByonly and the audit identity comes from that provider. Whenever it does not — no such provider registered, or one that returnednull/empty — the ownership key keeps fillingCreatedBy/UpdatedByexactly as before, so existing applications need no code change. - One DI call to wire it up —
AddDataOwnerProvider<TDbContext, TProvider>()registers the query filter, the hook, and yourIDataOwnerProviderimplementation together.TDbContextmust implementIDataOwnerDbContext; the requirement is compile-enforced by the method's generic constraint.
Quick start
public class Invoice : IOwnedBy
{
public string OwnedBy { get; private set; } = string.Empty;
}
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options), IDataOwnerDbContext
{
public IEnumerable<string> AccessibleKeys { get; init; } = [];
}
public sealed class TenantOwnerProvider(ICurrentTenant currentTenant) : IDataOwnerProvider
{
public string? GetOwnershipKey() => currentTenant.TenantId;
}
services
.AddDataOwnerProvider<AppDbContext, TenantOwnerProvider>()
.AddDbContextWithHook<AppDbContext>(options =>
options.UseSqlServer(connectionString)
.UseAutoConfigModel<AppDbContext>()); // required for the global query filter to apply
AddDataOwnerProvider<TDbContext, TProvider>() is declared where TDbContext : DbContext, IDataOwnerDbContext, so a
DbContext without the interface does not compile — and a context that reaches the query filter without it throws
InvalidOperationException at model-build time rather than quietly applying no ownership filter.
Customisation reference
There is no options class and nothing bound from appsettings.json — the whole surface is the three interfaces
you implement plus one DI call.
| Knob | Where | Default | Effect |
|---|---|---|---|
AddDataOwnerProvider<TDbContext, TProvider>() |
IServiceCollection |
not registered | Registers DataOwnerAuthQuery as a global model builder, TProvider as a scoped IDataOwnerProvider, and DataOwnerHook as a keyed hook for TDbContext. Constrained to DbContext, IDataOwnerDbContext. |
IDataOwnerDbContext.AccessibleKeys |
your DbContext |
you supply it | The keys the caller may read. Empty denies every owned row — it never means "all". |
IDataOwnerDbContext.IsUnrestrictedAccess |
your DbContext |
false (interface default) |
true bypasses the filter completely for that context. The only escape hatch. |
IDataOwnerProvider.GetOwnershipKey() |
your provider | required, no default | The owner key stamped on new IOwnedBy rows, and — unless an ICurrentUserProvider supplied a user for that save — on CreatedBy/UpdatedBy too. Null or blank means the hook stamps nothing. |
IDataOwnerProvider.GetAccessibleKeys() |
your provider | wraps GetOwnershipKey() into a single-key collection, or empty |
The keys ownership may be reassigned to. Override for callers that span several owners. |
DataOwnerAuthQuery.FilterKey |
fixed | nameof(DataOwnerAuthQuery) |
Named EF Core query-filter key; not configurable. |
DataOwnerAuthQuery.IsIgnorable |
fixed | false |
A specification's IgnoreQueryFilters() can never bypass ownership isolation. |
The hook stamps OwnedBy (and CreatedBy/CreatedOn on audited entities) only when they are still blank, keeps
an explicit SetUpdatedBy from being overwritten, and reverts an OwnedBy change that targets a key outside
GetAccessibleKeys().
User identity vs. ownership
Ownership and "who did this" are separate keys, and only ownership belongs here. To stamp CreatedBy/UpdatedBy
with the signed-in user rather than the tenant key, register an ICurrentUserProvider via
AddCurrentUserProvider<TDbContext, TProvider>() from
DKNet.EfCore.AuditLogs; that package documents the provider contract and
the identifier to return. Both providers are independent and optional — adding one changes neither the global
query filter nor the ownership-reassignment guard described above.
Full documentation, configuration options, and gotchas: DKNet.EfCore.DataAuthorization docs
| 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
- DKNet.EfCore.AuditLogs (>= 12.0.0)
- DKNet.EfCore.Extensions (>= 12.0.0)
- DKNet.EfCore.Hooks (>= 12.0.0)
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 |
|---|---|---|
| 12.0.0 | 0 | 9/17/2026 |
| 11.0.0 | 0 | 9/17/2026 |
| 10.1.29 | 0 | 9/17/2026 |
| 10.1.28 | 0 | 9/17/2026 |
| 10.1.27 | 42 | 9/16/2026 |
| 10.1.26 | 89 | 9/16/2026 |
| 10.1.25 | 35 | 9/16/2026 |
| 10.1.24 | 146 | 9/11/2026 |
| 10.1.23 | 74 | 9/11/2026 |
| 10.1.22 | 77 | 9/11/2026 |
| 10.1.21 | 109 | 9/11/2026 |
| 10.1.20 | 100 | 9/9/2026 |
| 10.1.19 | 103 | 9/3/2026 |
| 10.1.18 | 85 | 9/3/2026 |
| 10.1.17 | 89 | 9/3/2026 |
| 10.1.16 | 85 | 9/3/2026 |
| 10.1.15 | 93 | 9/1/2026 |
| 10.1.14 | 90 | 9/1/2026 |
| 10.1.13 | 89 | 8/31/2026 |
| 10.1.12 | 575 | 8/25/2026 |