There is no user base class or identity DbContext to inherit. The host module declares
[UsePackage<LocalIdentityPackage>], and the user is a plain entity, with the generator owning the
identity members and the EF configuration:
[PragmaticUser]
publicpartialclassAppUser
{
publicGuidId { get; set; }
publicstring? DisplayName { get; set; }
}
The EF stores (EfRolePermissionStore, EfGroupRoleStore) and the temporal entities documented
below do not need to know the user type. There is no JIT provisioning: creating a local
record on first external login is not in LocalIdentityPackage. If it is added, it will be
package-shaped, never an inheritance base.
[PragmaticUser] marks the application’s user entity. With Pragmatic.Identity.Persistence
referenced, the generator writes a resolver beside it, {User}Resolver, which loads the entity of the
signed-in caller:
The match.MatchProperty names the entity member compared with the caller: by default
ExternalIdentityKey, on the entity or on its IdentityRecord navigation. With MatchClaim = "sub"
and the default key, the caller’s side is ICurrentUser.Authentication.ExternalIdentityKey; any other
claim is compared as it is.
It reads through IReadRepository<TUser>, not a DbContext: the resolver lives in the module, and
the boundary’s context is generated in the host.
By a given key.FindByIdentityKeyAsync(key) is the same match for a key the caller holds rather
than the current user’s: what an IUserClaimsContributor does for the account being signed in, when
nobody is authenticated yet.
Registration. It is registered like any [Service] of the module (scoped, published to the host
through the module’s DI metadata) when the entity is public (the host names it from its own
assembly). So an action, a mutation or a service takes it as a dependency, a field included:
That is the lookup; do not write a second one against ExternalIdentityKey. UseUserCulture() also
registers it, with TryAdd, for a container built without the module’s registration.
A query reads it with [FromCurrentUser(member)].[FromCurrentUser(nameof(Employee.Id))] on a
[Query] property makes the query’s generated invoker construct the resolver (the generator knows
its constructor, so it does not ask DI for it), resolve the caller’s entity after validation and the
permission check, and write that member into the property before the read. No entity is 404, no
caller is 401. The member form needs the [PragmaticUser] entity in the same compilation as the query,
and this package referenced; otherwise PRAG0731 says why. The whole form:
Filtering by the caller.
Put [UsePackage<LocalIdentityPackage>] on the host module: the generator registers the EF stores
and composes the identity entities into the boundary DbContext.
For direct DI registration (a test host, or an application not using the package):
The preferred approach is to use the [UsePackage<LocalIdentityPackage>] attribute on your host module. The source generator integrates identity entities into your boundary DbContext via OwnsOne composition.
When temporal role/permission mappings change (e.g., a ValidTo is set to revoke early), invalidate the
affected cache entries through IPermissionCacheInvalidator: it targets the tags above so you never
enumerate users by hand:
await invalidator.InvalidateUserAsync(userId, ct); // this user only
}
}
InvalidateRoleAsync(role) / InvalidateGroupAsync(group) reach every affected user after a role’s
permission set or a group’s role set changes; InvalidateTenantAsync(tenantId) clears a whole tenant.
See Authorization → Permission Resolution.