Stratara.Identity.EntityFrameworkCore 4.6.0

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Stratara.Identity.EntityFrameworkCore --version 4.6.0
                    
NuGet\Install-Package Stratara.Identity.EntityFrameworkCore -Version 4.6.0
                    
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="Stratara.Identity.EntityFrameworkCore" Version="4.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Stratara.Identity.EntityFrameworkCore" Version="4.6.0" />
                    
Directory.Packages.props
<PackageReference Include="Stratara.Identity.EntityFrameworkCore" />
                    
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 Stratara.Identity.EntityFrameworkCore --version 4.6.0
                    
#r "nuget: Stratara.Identity.EntityFrameworkCore, 4.6.0"
                    
#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 Stratara.Identity.EntityFrameworkCore@4.6.0
                    
#: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=Stratara.Identity.EntityFrameworkCore&version=4.6.0
                    
Install as a Cake Addin
#tool nuget:?package=Stratara.Identity.EntityFrameworkCore&version=4.6.0
                    
Install as a Cake Tool

Stratara.Identity.EntityFrameworkCore

Derived. The behaviour described here is specified under openspec/specs/. Those specifications are the source; this page explains and illustrates them.

EF Core identity-directory plane for the Stratara stack: user↔tenant membership (many-to-many with tenant-scoped roles), active-tenant selection, and membership-backed authorization.

Contents

  • IdentityDirectoryDbContext<TContext> — standalone DbContext hosting all four directory tables (tenant_membership, active_tenant, setting_entry, api_key); derive a concrete context and own its migrations. To fold these into an existing context instead, call modelBuilder.ApplyIdentityDirectoryModel() in its OnModelCreating.
  • ModelBuilder.ApplyIdentityDirectoryModel() — adds the same tables to an existing context (for example your ASP.NET Identity context) so they share one migration lineage.
  • AddTenantMembershipStore<TContext>() — EF-backed ITenantMembershipStore: forward lookup (a user's tenants), reverse lookup (a tenant's members), upsert, GDPR sweeps per user/tenant, and membership-guarded active-tenant switching.
  • MembershipAuthorizationProvider / MembershipAuthorizationProvider<TUser> — the framework's default IAuthorizationProvider: role checks pass on tenant-scoped membership roles, the TUser variant additionally on global ASP.NET Identity roles (platform roles).
  • AddMembershipCrossTenantAuthorizer(...) — membership-backed ICrossTenantAuthorizer for strict tenant isolation, with configurable cross-tenant roles for the operator-impersonation path (platform administrators without membership in the target tenant).
  • AddPermissionCatalog(...) + AddCatalogPermissionResolver[<TUser>]() — the application's code-declared permission vocabulary with role grants, resolved from membership roles (and optionally global ASP.NET Identity roles) for [RequirePermission] enforcement and HTTP permission policies.
  • AddApiKeyStore<TContext>() — API keys and personal access tokens: stk_-prefixed 256-bit keys (hash-only storage), fail-closed validation with expiry/revocation, erasure sweeps. Machine keys are materialized as tenant memberships, so they flow through the same role/permission plane as human actors; PATs act as their bound user. ImportAsync stores a machine key the caller already holds (generated with ApiKeyFormat.CreateRawKey()) for setups where the key must exist before boot — idempotent, never mutating an already-stored key.
  • AddSettingCatalog(...) + AddSettingStore<TContext>() — scoped settings plane: code-declared SettingDefinitions, row-per-key EF storage (setting_entry), a Subject-fed ISettingProvider fallback chain (user-in-tenant → user → tenant → global → configuration → default), transparent AES-GCM encryption for IsEncrypted definitions, and per-user/per-tenant erasure sweeps.
  • AddTenantMembershipStoreFromContextFactory<TContext>(), AddApiKeyStoreFromContextFactory<TContext>(), AddSettingStoreFromContextFactory<TContext>() — the same three stores, taking a fresh context per operation from IDbContextFactory<TContext> instead of sharing the request's. See below.

One context for the request, or one per operation

The plain registrations give every directory store in a request the same context instance. A database context serves one operation at a time, so directory work issued concurrently inside one request fails on whichever operation arrives second — at the call site that lost the race, not the one that introduced the concurrency. Sharing also means a store's own commit commits whatever else the consumer has left unsaved on that context.

The …FromContextFactory variants remove both: each operation creates its own context and disposes it. In exchange, a store write no longer takes part in a transaction the consumer opened on their own scoped context. That trade is why the shared registration stays the default rather than being replaced.

They require AddDbContextFactory<TContext>(); a missing registration fails at first resolution. Keep the factory's options aligned with the scoped registration — interceptors, query filters and conventions are configured per registration, not per context type. Calling both variants for the same store leaves whichever ran first in place; they do not compose.

Dependencies

  • Microsoft.EntityFrameworkCore (+ Relational)
  • Microsoft.Extensions.Identity.Core
  • Stratara.Abstractions

Used together with Stratara.Mediator (authorizing mediator, tenant isolation) and Stratara.Identity.AspNetCore (sign-in claims bridge).

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 (1)

Showing the top 1 NuGet packages that depend on Stratara.Identity.EntityFrameworkCore:

Package Downloads
Stratara.EventSourcing.EntityFrameworkCore

EF Core persistence for the Stratara event-sourced stack on PostgreSQL — write-store, read-store, ASP.NET Identity store — including a shared identity store with its migrations — shared conventions, value generators, and UnitOfWork primitives. Targets Npgsql; maps pgvector types for a model that uses them, and needs no database extension otherwise.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.7.0 455 10/30/2026
4.6.1 82 10/29/2026
4.6.0 181 10/28/2026
4.5.0 263 10/28/2026
4.4.10 197 10/26/2026
4.4.9 105 10/26/2026
4.4.8 242 10/24/2026
4.4.7 113 10/23/2026
4.4.6 210 10/22/2026
4.4.5 643 10/14/2026
4.4.4 379 10/10/2026
4.4.3 115 10/10/2026
4.4.2 463 10/2/2026
4.4.1 422 9/29/2026
4.4.0 156 9/27/2026
4.3.1 131 9/25/2026
4.3.0 399 9/23/2026
4.2.0 162 9/18/2026
4.1.1 96 9/16/2026
4.1.0 113 9/16/2026
Loading failed

An erasure shreds every key that names the subject, and nothing stopped the next encryption for that subject
from creating a new one: a key store asked for a scope it does not hold creates a first version. A job, a request
in flight or a second process that encrypted for an erased subject afterwards got a fresh key, and what it wrote
stayed readable for good. The key store now records an erased subject and refuses to create a key for it again.

### Added

- **`IKeyStore.MarkSubjectErasedAsync(KeySubject)`** records that a tenant or user has been erased. It has a
 default implementation that throws `NotSupportedException`, so an existing key store still compiles.
- **`KeySubject`** names the tenant or user, and `KeySubject.Covers(KeyScope)` states which keys belong to its
 erasure: for a tenant, a key of any level that names it; for a user, a user-level key that names it.
- **`KeyScopeErasedException`** (an `InvalidOperationException`) is what a key store throws for such a scope. It
 carries the refused `Scope`.

### Changed

- **An encryption for an erased subject fails.** After a subject's erasure, `GetOrCreateCurrentKeyAsync` and
 `RotateAsync` throw `KeyScopeErasedException` for every scope the subject covers, whether or not it ever held a
 key, and create nothing. The blob encryptor, the encrypting settings store and the field-encrypting serializer
 pass it through. Code that erased a subject and then wrote encrypted data for it again now sees the exception;
 the mark is permanent and cannot be lifted. Revoking every version of a scope is not an erasure and marks nothing.
- **`ISubjectEraser` marks the subject before it sweeps any plane**, so an encryption racing the erasure
 either creates its key first, and that key is shredded with the rest, or is refused. A key store that cannot mark
 a subject leaves the erasure as before and logs `SubjectNotMarkable` (112009).
- **The file-backed key store writes format version 2**, which holds the marks, and refuses to open a file in a
 newer format than it knows. `InMemoryKeyStore` refuses an erased subject as the file store does; `DummyKeyStore`
 refuses to mark one, as it refuses every erasure operation.

**Upgrading:**
- **Upgrade every process that shares a key store file in one step.** A process on an earlier release reads a
 version-2 file, ignores the marks, creates keys for an erased subject and drops the marks the next time it writes
 the file. A version-1 file needs no migration. Rolling back is possible but loses the marks on the first write.
- **Map `KeyScopeErasedException` to your answer for data that is gone**, if a request can reach an encryption for
 a subject after its erasure.
- **A key store of your own implements `MarkSubjectErasedAsync`**, refusing in `GetOrCreateCurrentKeyAsync` and
 `RotateAsync` every scope a marked subject `Covers`. Without it the erasure warns and the gap stays open.