Skip to main content

Identity Boundaries in Wisej.NET

AccessOps opens on the blue AccessOps · Sign in window with the area strip Departments · Employees · Documents · Approvals · Admin console. After a demo sign-in the Documents screen of the walkthrough appears: a request bar with Tenant and Record boxes and the Open / Export buttons, the grid Record · Title · Classification · Tenant with tenant A's four documents (17 Supplier contract — Northwind renewal, 18 Q3 travel policy, 21 Board minutes — October, 24 Onboarding checklist) and a status line that turns idle, ok (green), deny (red) or warn (amber). Every read goes through DocumentService.Read(subject, tenant, recordId), which checks the current grant and the tenant before a read-counting repository runs. The admin console revokes and restores grants, shows the audit trail and CountingDocumentRepository.ReadCount, and in Debug builds opens the AccessOps · Identity diagnostics view (authentication type, IsAuthenticated, redacted subject, host request principal, effective OS identity).

Solution: AccessOps.slnx = AccessOps/ (Wisej.NET web app, net10.0, Wisej-4 4.1.7, Microsoft.Data.Sqlite 10.0.14) + PolicyHarness/ (plain .NET console, no Wisej). Data (SQLite database, audit-pseudonym key) is created in AccessOpsData/ beside the project, outside the web root, and is git-ignored. Modules 2 and 4 start from this folder.

Run it​

cd "Security and Auth Patterns Course\Module 1"
dotnet build AccessOps.slnx
dotnet run --project AccessOps --urls http://localhost:6401 # Debug build: http://localhost:6401
dotnet run --project PolicyHarness # the policy without Wisej, IIS or an IdP

No IIS, domain, identity provider or secret is needed. To start from fresh fixtures, stop the app and delete AccessOpsData/. The Debug console prints one AccessOps.IdentityTrace line per lifecycle point (startup, refresh, click, StartTask, Task.Run) — that is where the identity matrix comes from.

Demo accounts (invented, local only, Debug builds only)​

User nameSecretAccountGrant
reader.areader-demo-17acct …7f3a, tenant AReader grant …21, tenant A, read
unassignedunassigned-demoacct …19c8, tenant Anone
adminadmin-demo-21acct …d4b7, tenant AAdmin grant …44, administer, no read

TestVerifier is a training mock that never ships: it holds SHA-256 hashes of these demo secrets and exists only inside #if DEBUG. A Release build has no verifier and answers every sign-in with "Sign-in is not available in this build." Module 4 replaces it with AccessOps' own credential store.

Prove the diagnostics view is not packaged​

dotnet publish AccessOps/AccessOps.csproj -c Release -o $env:TEMP\accessops-m1-release
Select-String -Path $env:TEMP\accessops-m1-release\AccessOps.dll -Pattern IdentityDiagnosticsView,TestVerifier -SimpleMatch -Quiet # False

Result recorded in docs/release-check.md.

What to try​

Use two browser tabs (each tab is its own Wisej.NET session) or two browser profiles.

ActionExpected result
Tab 1: sign in as reader.a with a wrong secret"Sign-in failed." (red)
Tab 1: sign in as reader.a / reader-demo-17The page reloads (new session ID), Documents shows four rows, "Tenant A · 4 documents · signed in as a Reader"
Tab 1: Open with Tenant A, Record 17"Record 17 opened · Supplier contract — Northwind renewal" (green); ReadCount +1
Tab 1: Tenant B, Record 17, Open (fixture F02)"Access denied." (red), no record content; audit open B / 17 denied tenant; ReadCount +0
Tab 1: Tenant A, click row 17"Tenant A · record 17 selected"
Tab 1: Export"Record 17 exported · Supplier contract — Northwind renewal", record-A-17.txt downloads (the job ran on Task.Run with an explicit subject)
Tab 2: Admin console (not signed in)"Access denied."; in Debug the "Identity diagnostics" button is there
Tab 2: Identity diagnostics (not signed in)(none) · False · "none: host principal returned" · "not authenticated (Kestrel)" · the process account; "No override: the getter falls back to the host principal."
Tab 1: Admin console → Identity diagnostics (as reader)Portal · True · "acct …7f3a · tenant A" · same host principal and same OS identity as tab 2; "The session override answers Application.User."
Tab 2: Documents → sign in as admin / admin-demo-21, then Admin consoleGrants …44 admin and …21 reader.a, both Active; ReadCount; the audit list
Tab 2: select grant …21, Revoke"Reader grant …21 revoked." (amber); state Revoked; audit grant.revoke
Tab 1 (screen untouched, Export still visible): Export (fixture F05)"Access denied." (red); audit export A / 17 denied grant; ReadCount +0
Tab 2: Restore"Reader grant …21 restored." (green)
Tab 2: Fail next read, then Tab 1: Open"The document could not be loaded. Try again later." (amber); the log has the exception with the correlation ID and no identity; audit failed repository
New tab: sign in as unassigned / unassigned-demoDocuments: empty grid, "Access denied."; Open → "Access denied."; audit reason grant; ReadCount +0
dotnet run --project PolicyHarnessOne allowed read and five denies (tenant boundary F02, missing role, unknown, expired, revoked) at zero repository calls — docs/policy-harness-output.txt

Where this sample differs from the lesson or video​

  • Sign-in screen, admin console: only the Documents screen and the diagnostics view. The sign-in card (User name, Secret, Sign in) and the admin console (grants, audit, ReadCount, "Fail next read") are built in the same style because the lab needs them; the revocation only as a callout ("Admin console · another session").
  • ReadCount on screen: the video draws CountingDocumentRepository.ReadCount as an overlay beside the window; here it is in the admin console's repository strip.
  • Selected-row status: the run scene's "Tenant A · record 17 selected · the screen has not changed" is narration of that moment; the app says "Tenant A · record 17 selected".
  • Failure path: an unexpected repository failure is produced with the test repository's "Fail next read" seam (an injected exception), since nothing in a healthy SQLite file fails on demand.
  • Grid selection: Wisej's Bootstrap-4 theme draws the selected row grey with a focus outline, not the mock's light blue with a left accent bar.
  • ApplicationPrincipal: the pack's common/ApplicationPrincipal.cs sets Application.User and then calls RefreshSessionId(); this sample (like the lesson) rotates first, then sets the principal — and only on a page load, because from a WebSocket click the browser never adopts the new ID.
  • Audit inside the service: the ExportJob.cs writes the audit event in the job; here DocumentService writes one event per decision for every path, so the job cannot forget it.
  • Grant display: account IDs are shown with their last four characters (acct …7f3a), grant IDs with their last two (…21), matching the video.
  • PolicyHarness targets net10.0 (the pack's targets net8.0; the rule is identical) and adds the pack's missing-role, unknown and expired cases next to F02 and the revoked grant.
  • Effective OS identity shows the real process account of whoever runs the sample (the video shows the invented DEVBOX\dev).