Security Architecture: Identity, SSO, Authorization, Audit & Secure Deployment
A simulated corporate sign-in that hands the application a set
of claims, a ClaimsMapper that turns them into the application's own roles, a PermissionService.Demand
that checks the tenant guard first and the role store second, an audit log screen where every Demand
lands — granted or denied — and the failure path: an enabled Export button that the service refuses
anyway, with Missing permission: ExportData written to the trail.
Around that spine the lab adds tenant-role enforcement, export approval with separation of duties, safe HTML rendering of an untrusted customer note, and a security hardening checklist.
There is no real identity provider, no password field and no credential of any kind in this application. The sign-in gate picks which identity the provider should assert and shows the claim list that crosses the boundary. Nothing here is deployed anywhere.
Run it
cd "LearnWisej-Samples/Enterprise Wisej.NET Course\Module 10\EnterpriseOps"
dotnet run -f net10.0 --urls http://localhost:5210
Then open http://localhost:5210. (Visual Studio: open EnterpriseOps.slnx, press F5 — the port is in
Properties/launchSettings.json.) Requirements: .NET 10 SDK and the Wisej-4 4.1.0 NuGet package.
The screen
The sign-in gate (SignInGate) opens first — before any data is loaded. Pick an identity from the corporate
directory (its claims are listed on the right) and press Sign in, or Stay signed out to leave the
screen locked. To act as a second person at the same time, open the app in another browser or a private window
(a new session gets its own gate).
AuditLogPage ("EnterpriseOps — Work queue + audit"):
| Area | What it holds |
|---|---|
| Work queue (top left) | dgvWorkOrders, ✔ Approve (btnApprove), Approve pending export (btnApproveExport), ⤓ Export data (btnExport), and the result banner lblBanner |
| Customer note (top right) | the latest note on WO-1002, rendered by NoteRenderService with AllowHtml = false — the payload's tags show as characters |
| Audit log — sensitive commands (bottom) | the three filters User / Permission / Result, dgvAudit (Time · User · Tenant · Permission demanded · Result · Detail) and the dark status bar lblStatusBar |
Approve and Approve pending export appear only when Has says the caller holds the permission.
Export data is shown to every signed-in user: ExportService demands ExportData itself.
The directory to sign in as
| Account | Tenant | Role | Use it for |
|---|---|---|---|
m.weber | fabrikam | Manager | approvals, and requesting an export |
l.romero | fabrikam | Technician | the denied export |
j.kim | fabrikam | Auditor | reading the whole trail; releasing someone else's export |
d.singh | fabrikam | Admin | the separation-of-duties refusal |
svc.import | fabrikam | ServiceAccount | a non-human caller with edit rights only |
ana.ops | contoso | Manager | another tenant — sees contoso rows only |
t.novak | fabrikam | (none) | authenticated, entitled to nothing |
What to click
| # | Signed in as | Action | What you should see |
|---|---|---|---|
| 1 | l.romero | ⤓ Export data | failure (the walkthrough): red banner "You don't have permission to export data."; status bar UnauthorizedAccessException — Missing permission: ExportData · audited · nothing exported; the denial is the first row of the audit log |
| 2 | any | Result: DENIED filter | only refusals, including the seeded j.kim · ExportData · DENIED |
| 3 | m.weber | ✔ Approve | WO-… approved; two audit rows — ApproveWorkOrders · OK (the decision) and ApproveWorkOrder · OK (the change), same correlation id |
| 4 | m.weber | ⤓ Export data | 36 rows is above the 25-row threshold: amber banner, ExportData · PENDING · awaiting a second approver |
| 5 | d.singh, with no other export pending | ⤓ Export data, then Approve pending export | "An export cannot be approved by the person who requested it." — ApproveExport · DENIED · separation of duties |
| 6 | j.kim, in a second browser while an export is pending | Approve pending export | released: ApproveExport · OK and ExportData · OK · completed after approval |
| 7 | t.novak | sign in | amber banner naming the unmapped group CorpVPN-Users, an empty queue because Demand(ViewWorkOrders) refused |
Pending exports are shared by every session of the tenant (like the audit log), so the requester and the approver can be two people in two browsers.
Where things live
Module 10/
└─ EnterpriseOps/
├─ Domain/ WorkOrder · WorkOrderStatus · Priority · Tenant · WorkQueueRow · WorkOrderNote
├─ Security/ SecurityArchitecturePatterns.cs · PermissionService · RolePermissionStore · TenantGuard
│ ClaimsMapper · SsoIdentityProvider · SessionContext · CommandContext · Role
│ AuditLog · HtmlText · HardeningChecklist
├─ Services/ SignInService · WorkOrderService · ExportService · AuditQueryService
│ NoteRenderService · ServiceRegistry · CommandResult
├─ Data/ SeedData · InMemoryWorkOrderRepository · InMemoryNoteStore
├─ Diagnostics/ ActivityTrace — server-side log (System.Diagnostics.Trace)
├─ UI/ AuditLogPage.cs / .Designer.cs · SignInGate.cs / .Designer.cs
├─ docs/ the five deliverables + identity-flow.svg
├─ Program.cs session composition root — creates the graph, opens the page, signs nobody in
└─ Startup.cs Kestrel host (app.UseWisej(); *.json is never served)
Data: 40 in-memory work orders (36 fabrikam, 4 contoso), 3 customer notes on WO-1002 (the third carries an
injection payload), and 12 seeded audit entries including the five.