Skip to main content

TenantTool Web

# TenantTool Documentation

> A centralized management and auditing platform for Microsoft 365 tenants — built for managed service providers who operate fleets of customer tenants.

**Tech stack:** Blazor Server · .NET 8 · MudBlazor 9 · PostgreSQL · Microsoft Graph · PKCE OAuth2 · Docker + Caddy · ClosedXML

---

## Table of Contents

- [Architecture](#architecture)
- [Roles & Access](#roles--access)
- [Dashboard](#dashboard)
- [Tenant Management](#tenant-management)
- [Tenant Check](#tenant-check)
- [Specialized Reports](#specialized-reports)
- [Gold Config](#gold-config)
- [Standard Config](#standard-config)
- [Identity Config](#identity-config)
- [My Org](#my-org)
- [Authentication Flow](#authentication-flow)
- [Required API Permissions](#required-api-permissions)
- [Data Model](#data-model)

---

## Architecture

TenantTool is a Blazor Server application running against a PostgreSQL database. All Microsoft 365 operations use delegated PKCE OAuth2 flows — there is no application-only credential for per-tenant reads. Each registered tenant has its own Azure app registration.

| Component | Details |
|---|---|
| Rendering | Blazor Server interactive |
| Database | EF Core 8 + Npgsql (PostgreSQL) |
| Authentication | ASP.NET Core Identity for app login |
| Per-tenant auth | PKCE delegated OAuth2 |
| Token cache | 55 min TTL, 6 token types per tenant |
| Infrastructure | Docker + Caddy reverse proxy |
| Excel | ClosedXML for import/export |
| CI/CD | GitHub Actions → production |

### Multi-Client Hierarchy

The data model is scoped: **Clients** group related **Tenants**. Application users are assigned to a Client and can only see tenants belonging to that Client. Administrator-tier users (no `ClientId` set) see everything.

| Entity | Description | Notes |
|---|---|---|
| **Client** | An MSP customer organization | Holds logo and breakglass name pool preference |
| **Tenant** | An Azure / M365 tenant | Belongs to a Client; one per client can be flagged `IsGold` |
| **ApplicationUser** | A TenantTool login account | `ClientId = null` = full admin; set = scoped to one client |

---

## Roles & Access

Three application roles control what each user can see and do.

| Role | Name | Access |
|---|---|---|
| **Admin** | `TenantTool.Admin` | Full access — all pages including Clients, Users, Tenant registration, Standard Config, Gold Config, Identity Config, and all reports. Can provision tenants, manage app users, and update standard templates. |
| **L1** | `TenantTool.L1` | Restricted write access — can use Gold Config and Identity Config, run all reports, and perform Tenant Checks. Cannot manage clients, users, or tenant registrations. |
| **Report** | `TenantTool.Report` | Read-only — all reports and Tenant Check only. No write operations. |
| **Public** | *(anonymous)* | The Quick Check page is accessible without login using ad-hoc credentials. |

> **Warning:** The default seed account is `admin` / `admin`. Change the password immediately on first deploy.

---

## Dashboard

### Home — `/`

Shows three KPI tiles: total tenants, Gold tenants, and active app users. Lists all tenants visible to the current user. Click any tenant to open its hub page with links to Tenant Check and all reports.

**Access:** All roles

### Quick Check — `/quick-check`

Runs a full Tenant Check with ad-hoc credentials — Tenant ID, App Client ID, and client secret entered directly. No tenant registration required. Uses a client-credential (secret) flow instead of PKCE.

**Access:** No login required

### Info & Permissions Reference — `/info`

Lists the OAuth redirect URI to register in each tenant's app registration, and all required API permissions grouped by API surface (Graph, Exchange Online, Defender ATP, ARM, Azure Storage).

**Access:** All roles

---

## Tenant Management

### Tenants — `/tenants`

Register, rename, and delete Azure tenant entries. Two creation modes:

- **Manual** — paste in the Tenant ID, App Client ID, and client secret from an app registration you created in the Azure Portal.
- **Automatic provisioning** — authenticates as Global Admin via the Microsoft device code flow, then fully creates the `TenantToolWeb` app registration: all required permissions, admin consent, service principal with "assignment required", and a 24-month client secret. No Azure Portal work needed.

**Access:** Admin only

### Clients — `/clients`

Create, rename, and delete MSP client organizations. Visible to administrator-tier accounts only (users with a `ClientId` set cannot access this page).

**Access:** Admin only

### Users — `/users`

Create, edit, and delete TenantTool login accounts. Assign a role and optionally bind each user to a Client for data scoping.

**Access:** Admin only

---

## Tenant Check

### Full Tenant Analysis — `/tenant-info/{id}`

Authenticates via PKCE, then fetches all tenant data from Microsoft Graph in parallel across eight analysis tabs.

**Access:** All roles

| Tab | What it checks |
|---|---|
| **Summary** | Health status overview per platform, tenant country, technical contact, creation date, verified domains |
| **Admin** | Directory roles, Global Admins, breakglass accounts, MFA coverage for privileged users |
| **Identity** | All users, deleted users, risky users, MFA/SSPR registrations, sign-in log analysis, license overview |
| **Intune** | Managed devices, configuration profiles, compliance policies with assignment and success/error/conflict counts |
| **Exchange** | EOP policies (Anti-Malware, Anti-Spam, Anti-Phishing, Safe Links, Safe Attachments, Quarantine), Secure Score, mailbox sizes |
| **Files** | SharePoint tenant settings, OneDrive quota overview, sharing and external-access policies |
| **Defender** | Open alerts, active incidents, endpoint status, exposure level from Microsoft Defender ATP |
| **Teams** | Teams calling settings, channel overview, membership summary |
| **Gold Check** | Compares this tenant against the Gold baseline — per Intune profile, compliance policy, and CA policy: Match / Differ / Missing / Extra |

A **standalone HTML report** can be exported containing all results with embedded styles and the client's logo. The file is fully self-contained and can be emailed or archived without dependencies.

---

## Specialized Reports

Each report authenticates via PKCE and is accessible from the tenant hub. All reports include an export to a standalone HTML file.

| Report | Route | Details |
|---|---|---|
| **SharePoint Rights** | `/tenant-sharepoint/{id}` | Select sites, then fetch Owners / Members / Visitors groups and expand Azure AD security group members |
| **SharePoint Storage** | `/tenant-sp-storage/{id}` | Per-site and per-OneDrive storage used vs. allocated. Export HTML + JSON |
| **Azure Blob Storage** | `/tenant-blob-storage/{id}` | Uses ARM + Azure Storage tokens to list accounts, containers, and blob tree with file sizes. Export HTML + JSON |
| **Exchange Mailboxes** | `/tenant-exchange/{id}` | Shared mailboxes with full-access and read-only delegate permissions via Exchange Online token |
| **Teams** | `/tenant-teams/{id}` | All Teams, their channels, owners, and members |
| **Conditional Access** | `/tenant-conditional-access/{id}` | All CA policies with state, conditions summary, and grant controls |
| **Privileged Access** | `/tenant-privileged/{id}` | All Entra ID directory roles and their current holders |
| **PIM Status** | `/tenant-entra/{id}` | PIM-eligible vs. permanent role assignments; identifies over-privileged accounts |
| **Maintenance** | `/tenant-maintenance/{id}` | Stale users, inactive devices, and other maintenance-relevant findings |

---

## Gold Config

### Gold Reference Baseline — `/gold-config`

Designate one tenant per client as the **Gold** tenant — the approved configuration baseline. TenantTool syncs that tenant's Intune configuration profiles, compliance policies, and Conditional Access policies into the local database.

Once synced, compare any other tenant against the Gold baseline. Each item shows one of four states:

| State | Meaning |
|---|---|
| ✅ **Match** | Configuration is identical to Gold |
| ⚠️ **Differ** | Item exists but configuration differs |
| ❌ **Missing** | Item exists in Gold but not in this tenant |
| ℹ️ **Extra** | Item exists in this tenant but not in Gold |

Comparison is by display name. Use the **Gold Check** tab in Tenant Check to quickly spot configuration drift across managed tenants.

**Access:** Admin + L1

---

## Standard Config

### Provisioning Templates — `/standard-config`

Defines the templates used when provisioning new or existing tenants. Five tabs, all supporting JSON export and import.

**Access:** Admin only

| Tab | What it defines | Import formats |
|---|---|---|
| **Admin Users** | Admin and breakglass accounts — UPN prefix, first/last name, department, company, job title, security group rights, usage location | JSON, Excel |
| **Admin Groups** | RBAC-bearing groups with role assignments | JSON, Excel |
| **Standard Groups** | Security / M365 groups with type (Assigned or Dynamic), category, and membership rules | JSON, Excel |
| **CA Policies** | Conditional Access policy templates stored as JSON. Full typed form editor. Import directly from a live tenant | JSON, from Tenant |
| **EOP Policies** | Exchange Online Protection templates — Anti-Malware, Anti-Spam Inbound/Outbound, Anti-Phishing, Safe Links, Safe Attachments, Quarantine — with per-field typed editors | JSON |

### Importing CA Policies from a Live Tenant

The CA Policies tab includes an **Import from Tenant** button. Select a source tenant, authenticate via PKCE (scope: `Policy.Read.All`), and TenantTool fetches all CA policies from Graph API. Pick which ones to bring in as standard templates.

Server-only fields (`id`, `createdDateTime`, `modifiedDateTime`, `templateId`) are stripped automatically. Existing templates with the same name are updated; new ones are added.

---

## Identity Config

### Tenant Identity Provisioning — `/tenant-config/identity`

Applies standard templates to a specific tenant via Graph API. Four sub-tabs.

**Access:** Admin + L1

---

### Group Provisioning

Select standard groups (admin and/or standard groups) and provision them in a chosen tenant. Groups are created with the configured type, membership rule, and category. Groups that already exist are skipped.

### Admin User Provisioning

Create admin and breakglass accounts from standard templates. A temporary password is generated per account. Manager assignments are set automatically when a manager UPN is specified in the template.

### Member User Provisioning

Import a member user Excel file and create accounts in bulk.

**Required columns:** First Name, Last Name, UserPrincipalName, Company Name, Usage Location

**Optional columns:** Job Title, Department, Manager (UPN)

Group memberships and manager relationships are set after account creation. Results are shown per-user with per-assignment status.

### CA Policy Deployment

Select standard CA policies and deploy them to a target tenant.

**Workflow:**

1. **Check for duplicates** — any policy with the same display name on the target tenant is skipped and flagged "Already exists".
2. **Resolve references** — group names, user UPNs, and named location names are looked up in the target tenant and replaced with their object IDs. Unresolvable references block deployment for that policy.
3. **Create policy** — the resolved JSON is posted to `/identity/conditionalAccess/policies` on the target tenant. New policies are always created as **disabled**.

> **Note:** CA policies are always created as **disabled**. Review and enable them manually in the tenant's Azure portal to prevent accidental lockout.

#### In-session customization

Before deploying, policies can be customized without modifying the stored standard templates:

- **Edit** — open a per-policy JSON editor. Changes are stored in memory for the current session only.
- **Reset** — discard in-session edits and revert to the standard template.
- **Export JSON** — download the current working set (including any in-session edits) as a JSON file.
- **Import JSON** — load a previously exported or manually crafted JSON file. Policies matching existing standard names override their JSON for the session; unrecognized names are added as temporary session-only policies.

---

## My Org

### Organisation Settings — `/my-org`

**Client Logo** — Upload a PNG or SVG (max 2 MB). The logo is stored as Base64 and embedded in all exported HTML reports for that client.

**Breakglass Name Pool** — Choose a themed name pool for breakglass account naming suggestions:

- Saint Seiya
- Greek & Roman Gods
- Norse Mythology
- Arthurian Legends

Each pool contains 50 names. Use "Pick Random" to suggest a name and copy it to clipboard.

**Access:** Admin only

---

## Authentication Flow

Each report and provisioning operation uses a **PKCE delegated OAuth2 flow**. The token cache is checked first; if no valid token exists, the user is redirected to Microsoft's authorization endpoint for that specific tenant.

```
1. TenantTool generates a PKCE code verifier + SHA-256 challenge
   and redirects to:
   login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize

2. After Microsoft sign-in and consent, the browser is redirected
   to /oauth-callback with an authorization code.

3. The callback fires six parallel token exchanges:
   - Microsoft Graph
   - Exchange Online
   - Microsoft Defender ATP
   - SharePoint REST
   - Azure Resource Manager
   - Azure Storage

4. All tokens are stored in TokenCacheService keyed by (userId, tenantDbId)
   with a 55-minute TTL. The user is returned to the originating page.
```

> After 55 minutes, the next operation will trigger re-authentication. Each browser session maintains its own token set per tenant.

---

## Required API Permissions

The following **delegated** permissions must be granted with admin consent on each tenant's app registration. The Automatic Provisioning flow creates all of these automatically.

### Microsoft Graph — Delegated

```
User.Read                                   Organization.Read.All
User.Read.All                               User.ReadWrite.All
Group.Read.All                              Group.ReadWrite.All
Directory.Read.All                          Sites.Read.All
Sites.ReadWrite.All                         DeviceManagementManagedDevices.Read.All
DeviceManagementConfiguration.Read.All      DeviceManagementApps.Read.All
RoleManagement.Read.Directory               Policy.Read.All
Policy.ReadWrite.ConditionalAccess          IdentityRiskyUser.Read.All
Reports.Read.All                            AuditLog.Read.All
UserAuthenticationMethod.Read.All           SecurityEvents.Read.All
SecurityIncident.Read.All                   SharePointTenantSettings.Read.All
TeamMember.Read.All                         Channel.ReadBasic.All
ChannelMember.Read.All                      TeamSettings.Read.All
TeamsActivity.Read
```

### Office 365 Exchange Online — Delegated

```
Exchange.Manage
```

### Windows Defender ATP — Delegated

```
Alert.Read
Machine.Read
```

### Azure Service Management — Delegated

```
user_impersonation
```

### Azure Storage — Delegated

```
user_impersonation
```

---

## Data Model

Key entities persisted in PostgreSQL via Entity Framework Core.

| Entity | Key Fields | Purpose |
|---|---|---|
| **Client** | `Name`, `LogoBase64`, `BreakglassNamePool` | MSP customer organization; holds logo and name pool preference |
| **Tenant** | `AzureTenantId`, `AppClientId`, `ClientSecret`, `IsGold` | Azure / M365 tenant registration; one per client can be Gold |
| **ApplicationUser** | `ClientId` (nullable), `Role` | TenantTool login; `null` ClientId = administrator |
| **StandardUser** | `Type`, `UpnPrefix`, `FirstName`, `LastName`, `SecurityGroupsRights` | Admin / breakglass user provisioning template |
| **StandardAdminGroup** | `DisplayName`, `Description`, `RbacRole` | RBAC group provisioning template |
| **StandardGroup** | `DisplayName`, `GroupType`, `GroupCategory`, `MembershipRule` | Standard group provisioning template |
| **StandardCaPolicy** | `Name`, `Comment`, `ConfigJson` | CA policy template (full Graph API JSON payload) |
| **StandardEopPolicy** | `PolicyType`, `Name`, `Comment`, `ConfigJson` | EOP policy template by type |
| **GoldSync** | `TenantId`, `LastSyncedAt` | Tracks last Gold sync timestamp per tenant |
| **GoldConfigEntry** | `ExternalId`, `Name`, `Category`, `Platform`, `SettingsJson` | Intune configuration profile snapshot from Gold tenant |
| **GoldComplianceEntry** | `ExternalId`, `Name`, `Category`, `Platform` | Intune compliance policy snapshot from Gold tenant |
| **GoldCaEntry** | `ExternalId`, `DisplayName`, `State`, `IncludeGroupsJson` | CA policy snapshot from Gold tenant |

---

*TenantTool · Blazor Server .NET 8*