Sample 8 — Identity Directory (membership, permissions, settings)
Derived page. The behaviour described here is specified by the
tenant-directorycapability underopenspec/specs/. That specification is the source; this page explains and illustrates it. Where the two disagree, the specification is right and this page is a bug.
Concept: Stratara.Identity.EntityFrameworkCore — who belongs to which tenant, what they may do
there, and what they have configured. Three planes over the same EF tables and the same session
Subject.
- Code:
samples/Stratara.Sample.IdentityDirectory - Lines: ~200
- Read time: 10–15 min
- What it doesn't have: no HTTP, no sign-in — a console program on in-memory SQLite that sets the session Subject by hand.
What you'll see
DirectoryDbContext— three lines. Deriving fromIdentityDirectoryDbContext<T>brings the directory tables. (Sample 7 shows the other hosting option: fold them into an existing context withApplyIdentityDirectoryModel().)- Membership — Alice is seeded as
TenantAdminin Acme andViewerin Globex; Bob asViewerin Acme. Both lookup directions are exercised (a user's tenants, a tenant's members), plus the membership-guarded active-tenant selection. Simulations.cs— a query and a command guarded with[RequirePermission("sims.read")]/[RequirePermission("sims.delete")]. The handlers contain no authorization code.SampleSessionContextProvider— stands in forSessionContextMiddleware, which in a web host derives the Subject from thestratara:tenant_idclaim.- Settings —
Ui.Theme(inherited) andUi.Density(IsInherited: false) resolved for three different (user, tenant) pairs.
Running
dotnet run --project samples/Stratara.Sample.IdentityDirectory
Expected output, abridged:
--- 1. Membership: one user, many tenants, roles scoped per tenant ---
Alice in Acme: [TenantAdmin]
Alice in Globex: [Viewer]
Acme members: 2
Alice's active tenant: Globex
--- 2. Permissions: membership roles mapped through the catalog ---
Alice @Acme — delete: allowed
Bob @Acme — delete: DENIED (missing sims.delete)
Alice @Globex — delete: DENIED (missing sims.delete)
--- 3. Settings: user-in-tenant -> user -> tenant -> global -> config -> default ---
Alice @Acme — Ui.Theme=dark, Ui.Density=compact
Bob @Acme — Ui.Theme=high-contrast, Ui.Density=comfortable
Alice @Globex — Ui.Theme=system, Ui.Density=comfortable
Key takeaways
- The two
Alicelines are the whole point. Identical user id, opposite outcome — because roles hang off the membership, not the account. Modelling "admin here, read-only there" needs no second account and no role-name prefixing. - Permissions are declared code-first and granted to roles.
GrantToRoleon an undeclared permission throws at startup, so a typo fails the boot instead of becoming a silent deny in production. [RequirePermission]only bites when an authorizing mediator and anIPermissionResolverare registered — and the startup validator throws if a guarded type exists without them. A guard cannot be silently skipped.Ui.Themeis inherited, soAlice @Acmefalls through to Acme'sdarkwhileBob @Acmekeeps his ownhigh-contrast.Ui.Densityis declaredIsInherited: false, so Bob does not inherit Acme'scozy— he lands on the code default. That flag models a setting that must be answered per user or not at all.
See the Tenant Membership, Permission-Based Authorization and Scoped Settings guides for the full walkthrough, and Sample 7 for how a caller gets authenticated in the first place.