Architecture
Two independent auth planes
The design keeps who you are and what the bridge may do in Microsoft's cloud completely separate:
| Plane | Who | How |
|---|---|---|
| Operator plane | Your technicians using the SPA + API | OIDC (JWT bearer) against your own identity provider, or self-registered local accounts with passkeys/TOTP and per-tenant sharing -- no IdP required. See Authentication for the full local-account model. The operator's name from the token is what lands in workflow run history either way. |
| Microsoft plane | The bridge acting in customer tenants | A multi-tenant Entra app under the Secure Application Model.
SamTokenService exchanges the stored (encrypted, auto-rotated) SAM refresh
token for a per-tenant Graph token on demand, scoped by that customer's GDAP roles. |
Module layout
web/ (React+Vite+TS) --> src/PartnerCenterBridge.Api --> Core (contracts / desired state / reconcile / workflows)
|- Graph (tenant client factory, Intune Win32, users, Identity workflows)
|- Exchange (EXO PowerShell V3 out-of-process, Mailbox workflows)
|- PartnerCenter (SamTokenService, PartnerCenterClient)
|- Data (EF Core + Postgres, encrypted secrets, run history)
| Project | Responsibility |
|---|---|
Core |
Domain entities, reconcile engine, the IWorkflow contract and catalog.
No external SDK dependencies - everything else depends on Core, never the reverse. |
Data |
EF Core BridgeDbContext + migrations. Secrets (like the SAM token) are
encrypted at rest via ASP.NET Data Protection. Workflow runs and deployments live here. |
PartnerCenter |
SamTokenService (MSAL SAM flow + token rotation), Partner Center REST
client (customer list). |
Graph |
Per-tenant Graph clients, the full Intune Win32 upload state machine, hire/offboard user service, and the Identity workflows. |
Exchange |
What Graph can't do: mailbox configuration through the Exchange Online PowerShell V3 module (app-only certificate), run out-of-process, plus the Mailbox workflows. |
Api |
Controllers, OIDC auth, DI wiring, deploy + provisioning orchestration, workflow dispatch + run recording. |
web/ |
React SPA: Tenants, Contracts, App Templates, Deploy, History, New Hire, Offboard, Workflows. |
The contract model
A contract is a named desired state shared by a set of tenants: which Win32 apps they should have (at which version), and how new users are provisioned. The reconcile engine compares desired state against what each tenant actually has and produces a plan - deploy, update, or nothing - that the operator reviews before it runs. Pushing a new template version fans the update out to every tenant on the contract.
The workflow engine
Workflows are the other half: not desired state, but known fixes for the problems that land in MSP queues every week. Each implements one interface:
public interface IWorkflow
{
string Id { get; } // "mfa-reset"
string Name { get; }
string Description { get; }
string Category { get; } // "Identity", "Mailbox"
IReadOnlyList<WorkflowInput> Inputs { get; } // rendered generically by the UI
Task<DiagnosisResult> DiagnoseAsync(Tenant tenant, IReadOnlyDictionary<string,string> inputs, CancellationToken ct);
Task<WorkflowRunResult> RemediateAsync(Tenant tenant, IReadOnlyDictionary<string,string> inputs, CancellationToken ct);
}
Implementations live in the backend project that owns the API they call (Graph or Exchange) and register through DI. The API lists and dispatches them uniformly and records every run; the UI renders each workflow's input schema. Adding a workflow requires zero controller or frontend changes. See Workflows for the catalog.
Audit trail
Every diagnose and remediate run is persisted: workflow, tenant, operator identity, inputs,
every finding, every step, outcome, duration. Failures can additionally push to a webhook
(Teams Adaptive Card or plain JSON - Notifications config section). Show-once
secrets like generated temporary passwords are deliberately excluded from both.