In this module
MSA1.2 Identity Types in Entra ID
You've created user accounts in Entra ID. You know that guest users look different from member users in the admin center. You may have seen service principals when registering an application. This sub teaches you to think about identity types as architectural categories — five distinct categories, each with a different authentication flow, a different Conditional Access evaluation path, different sign-in log behavior, and different lifecycle management requirements. The distinction matters because a security architecture that treats all identities the same has blind spots that attackers exploit — and those blind spots are invisible unless you know what to look for.
Every M365 tenant contains more identities than the "user" count suggests. Cloud-only accounts, synced accounts, guest accounts, application registrations, service principals, managed identities — each type authenticates through a different mechanism, each appears differently in sign-in logs, each evaluates differently against Conditional Access policies, and each requires different lifecycle management. If you don't know what's in your tenant — an exact census of each type, the properties that distinguish them, and the architectural implications of each — you can't design security controls that cover all of them. What you can't see, you can't protect.
Estimated time: 45 minutes.
Why identity types matter architecturally
When you open the Entra admin center and navigate to Users, you see a list. Names, email addresses, account status. The list looks uniform — each entry is a "user." The admin center's default view hides the most architecturally significant property: these are not the same kind of identity.
A cloud-only user authenticates directly against Entra ID. A synced user authenticates against Entra ID using a password hash that originated in on-premises Active Directory. A guest user authenticates against their home tenant — a completely different Entra ID instance that you don't control — and presents a cross-tenant token. Each of these three "users" goes through a different authentication flow, produces different fields in the sign-in log, and evaluates differently against the same Conditional Access policy.
And those are just the human identities. The admin center's Users blade doesn't prominently show the non-human identities at all — application registrations, service principals, and managed identities live in separate blades (App registrations and Enterprise applications). These non-human identities authenticate through the OAuth 2.0 client credentials flow — no interactive sign-in, no browser, no device, no MFA prompt — and they bypass every CA policy that targets "All Users" because they're not users. They're applications.
The architectural consequence is that a security architect who designs CA policies, detection rules, and lifecycle management for "users" — without distinguishing between the five identity categories — has designed controls that cover part of the tenant and leave the rest exposed. This sub teaches you to see the full picture.
Category 1 — Cloud-only users
A cloud-only user is a user account created directly in Entra ID with no connection to an on-premises Active Directory. Its authoritative source is Entra ID itself. Its password (if it has one — passwordless accounts may not) is stored as a hash in Entra ID. Its authentication happens entirely in the cloud.
The Graph API distinguishes cloud-only users by the absence of on-premises properties. Run this against your tenant to find a cloud-only user and examine its properties:
Connect-MgGraph -Scopes "User.Read.All","AuditLog.Read.All"
# Find cloud-only member users in YOUR tenant
$cloudOnlySample = Get-MgUser -All -Filter "userType eq 'Member'" `
-ConsistencyLevel eventual `
-Property DisplayName, UserPrincipalName, UserType,
OnPremisesSyncEnabled, OnPremisesDistinguishedName,
OnPremisesDomainName, OnPremisesLastSyncDateTime,
CreatedDateTime, CreationType, AccountEnabled,
SignInActivity |
Where-Object { $_.OnPremisesSyncEnabled -ne $true } |
Select-Object -First 1
# Display with all architecturally significant fields
$cloudOnlySample | Select-Object DisplayName, UserPrincipalName, UserType,
OnPremisesSyncEnabled, OnPremisesDistinguishedName,
OnPremisesDomainName, OnPremisesLastSyncDateTime,
CreatedDateTime, CreationType, AccountEnabled,
@{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}},
@{N='LastNonInteractive';E={$_.SignInActivity.LastNonInteractiveSignInDateTime}}How to interpret the output — what each field tells you:
OnPremisesSyncEnabled: null — this identity was not created by directory synchronization. It has no on-premises counterpart. If you have an on-premises AD and it's compromised, this identity is unaffected. If your sync infrastructure fails, this identity is unaffected. It exists only in Entra ID.
OnPremisesDistinguishedName: null — no AD Distinguished Name. This confirms the identity was never in Active Directory. If you do see a value here (something like CN=Jane Smith,OU=IT,DC=contoso,DC=local), the account is synced, not cloud-only — the filter above should exclude these, but verify.
CreationType: null — a null value means this is a regular work/school account created through the standard admin process. If you see Invitation, it's a guest account. If you see LocalAccount, it's a B2C account. The null value distinguishes cloud-only members from all other identity creation methods.
LastSignIn — when the user last authenticated interactively. For break-glass accounts, this should be rare (they're emergency-only). For regular users, a null or very old value indicates a stale account (MSA1.8 covers staleness detection).
LastNonInteractive — when an application last refreshed a token on behalf of this user. If LastNonInteractive is recent but LastSignIn is weeks old, the user's applications are maintaining sessions silently — normal for users who stay signed in on compliant devices.
Now count all cloud-only members:
$cloudOnly = Get-MgUser -All -Filter "userType eq 'Member'" `
-ConsistencyLevel eventual `
-Property DisplayName, OnPremisesSyncEnabled, CreatedDateTime |
Where-Object { $_.OnPremisesSyncEnabled -ne $true }
Write-Host "Cloud-only member users: $($cloudOnly.Count)"
# Show creation distribution by year
$cloudOnly | Group-Object { $_.CreatedDateTime.Year } |
Sort-Object Name |
Select-Object @{N='Year';E={$_.Name}}, Count |
Format-Table -AutoSizeReading your results:
If all your members are cloud-only (count equals your total member users), you're a cloud-only organization. You have no hybrid identity complexity — no sync infrastructure, no on-premises credential propagation risk, no Tier 0 sync servers to protect. Your identity architecture is simpler, but you still need to account for guests and workload identities.
If most members are cloud-only with a few synced, you're in a hybrid migration — the synced users are legacy accounts still connected to on-premises AD. MSA1.4 (hybrid architecture) is critical for you to understand the security properties of that connection.
If most members are synced with a few cloud-only, you're a traditional hybrid organization. The cloud-only accounts are likely break-glass accounts and recent hires created directly in the cloud. The synced majority means on-premises AD security directly affects your cloud security.
The year distribution reveals your migration history. If cloud-only accounts only appear from a certain year forward, that's when your organization started creating identities directly in the cloud rather than through AD sync.
Architectural significance: Cloud-only users are the simplest identity type to manage. They authenticate against one system (Entra ID). Identity Protection evaluates their risk fully. CA policies evaluate their sign-ins completely. SSPR handles their password resets. No sync dependency, no on-premises exposure. Break-glass accounts should always be cloud-only — they must work even if the hybrid sync infrastructure fails entirely.
Entra Admin Center
Identity → Users → All users → add a column filter for On-premises sync enabled
Filter to No to see cloud-only users. Filter to Yes to see synced users. The portal doesn't show `signInActivity` in the default columns — you need the Graph API query for staleness analysis.
Compare your cloud-only count against your total members. If you see a small number of cloud-only accounts in a tenant that migrated from on-premises AD, those are likely post-migration hires and break-glass accounts. A high synced percentage (80%+) means most of your identity population inherits on-premises AD risk — a compromise of the AD forest cascades into Entra ID.
Category 2 — Synced users
A synced user originated in on-premises Active Directory and was replicated to Entra ID by the sync engine (Cloud Sync or legacy Entra Connect). The on-premises AD is the source of authority — the user's attributes, group memberships, and (with Password Hash Sync) password hash are replicated to the cloud.
Run this to find a synced user in your tenant and examine the on-premises properties:
# Find a synced user in YOUR tenant (returns nothing if cloud-only)
$syncedSample = Get-MgUser -All -Filter "onPremisesSyncEnabled eq true" `
-ConsistencyLevel eventual `
-Property DisplayName, UserPrincipalName, UserType,
OnPremisesSyncEnabled, OnPremisesDistinguishedName,
OnPremisesDomainName, OnPremisesLastSyncDateTime,
OnPremisesSamAccountName, OnPremisesSecurityIdentifier,
CreatedDateTime, AccountEnabled, SignInActivity |
Select-Object -First 1
if ($syncedSample) {
$syncedSample | Select-Object DisplayName, UserPrincipalName,
OnPremisesSyncEnabled, OnPremisesDistinguishedName,
OnPremisesDomainName, OnPremisesLastSyncDateTime,
OnPremisesSamAccountName,
@{N='OnPremSID';E={$_.OnPremisesSecurityIdentifier}},
CreatedDateTime, AccountEnabled,
@{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}}
} else {
Write-Host "No synced users found — your tenant is cloud-only."
Write-Host "This means you have no hybrid identity complexity."
Write-Host "Skip to the interpretation guidance below."
}How to interpret the on-premises properties (if you have synced users):
OnPremisesSyncEnabled: true — this identity is actively synced. Changes to the on-premises AD object propagate to Entra ID within the sync cycle. For Cloud Sync, the default cycle is 2 minutes. For legacy Entra Connect, it's 30 minutes.
OnPremisesDistinguishedName — reveals the user's exact position in the on-premises AD hierarchy. The OU path tells you their department, site, or organizational placement. This property is read-only in Entra ID — you can't change it from the cloud because on-premises AD is the source of authority for synced objects.
OnPremisesDomainName — the on-premises AD domain name. If this is a .local domain (e.g., contoso.local), the organization deployed AD before Microsoft recommended routable domain names. The sync engine maps the internal UPN suffix to the cloud UPN. If it's a routable domain (e.g., contoso.com), the UPN matches between on-premises and cloud.
OnPremisesLastSyncDateTime — when this user's attributes last synced. If this timestamp is stale (hours or days old), the sync infrastructure may be broken. Entra ID would be operating on outdated identity data — password changes, account disables, and group membership changes from on-premises wouldn't be reaching the cloud.
OnPremisesSamAccountName — the legacy NT4-style logon name. This is what appears in on-premises Windows Security Event logs. Correlating cloud sign-in logs (which use UPN) with on-premises event logs (which use sAMAccountName) requires knowing both values.
Now count all synced users and check sync health:
$synced = Get-MgUser -All -Filter "onPremisesSyncEnabled eq true" `
-ConsistencyLevel eventual `
-Property DisplayName, OnPremisesLastSyncDateTime, AccountEnabled
Write-Host "Synced users: $($synced.Count)"
if ($synced.Count -gt 0) {
# Check sync health
$latestSync = ($synced | Sort-Object OnPremisesLastSyncDateTime -Descending |
Select-Object -First 1).OnPremisesLastSyncDateTime
$syncAge = (Get-Date).ToUniversalTime() - $latestSync
Write-Host "Latest sync: $latestSync ($([int]$syncAge.TotalMinutes) minutes ago)"
# Check for disabled synced accounts
$disabledSynced = ($synced | Where-Object { -not $_.AccountEnabled }).Count
Write-Host "Disabled synced accounts: $disabledSynced"
} else {
Write-Host "Your tenant has no synced users — you're cloud-only."
Write-Host "MSA1.4 (hybrid architecture) and MSA1.5 (legacy cleanup)"
Write-Host "are still worth understanding as reference knowledge, but"
Write-Host "the remediation actions won't apply to your environment."
}Reading your results:
If synced users = 0, you're cloud-only. You don't need to worry about on-premises compromise propagation, sync server security, or PHS vs PTA decisions. MSA1.4 and MSA1.5 are reference knowledge for you — important to understand but not actionable in your environment.
If synced users are the majority (>50% of members), on-premises AD security is cloud security. A compromise of your domain controllers gives the attacker cloud credentials. MSA1.4's hybrid architecture decisions (which sync engine, which auth method) are critical.
If you have disabled synced accounts, these are leavers whose on-premises accounts were disabled but not deleted. The sync engine replicates the disabled state. MSA1.8 covers these — disabled accounts with residual group memberships are a specific risk.
Check the sync age. For Cloud Sync, anything under 30 minutes is healthy. For legacy Entra Connect, under 60 minutes is expected. If the sync age is hours or days, investigate — your cloud directory may be out of date.
Check your synced user count and the last sync time. Disabled synced accounts are common — leavers whose AD accounts were disabled but whose Entra ID objects persist with active group memberships. These are stale identities with residual access, covered in MSA1.8. A heavily hybrid tenant (80%+ synced) means on-premises AD compromise would affect the majority of your identity population.
Architectural significance: Synced users are the most complex identity type because they exist in two systems simultaneously. Their password hash lives in both on-premises AD and Entra ID (with PHS). Their group memberships are replicated. Their attributes flow from on-premises to cloud. This dual existence means that on-premises AD security is cloud security — any compromise of the on-premises environment propagates through the sync pipeline. MSA1.3 covers the specific attack techniques. MSA1.4 covers the sync architecture decisions.
Category 3 — Guest users (B2B)
A guest user is an external identity invited into your tenant via Entra ID B2B collaboration. The critical architectural distinction: the guest's identity lives in their home tenant. Your tenant contains a representation of the guest — a user object with UserType: Guest — but the actual identity, the credential, the authentication policy, and the MFA registration all belong to the home tenant.
# Query guests in YOUR tenant
$guests = Get-MgUser -All -Filter "userType eq 'Guest'" `
-Property Id, DisplayName, UserPrincipalName, Mail, UserType,
ExternalUserState, ExternalUserStateChangeDateTime,
CreationType, CreatedDateTime, AccountEnabled, CompanyName,
SignInActivity
Write-Host "Total guest users: $($guests.Count)"
if ($guests.Count -gt 0) {
# Break down by state
$guests | Group-Object ExternalUserState |
Sort-Object Count -Descending |
Select-Object @{N='State';E={if($_.Name){"$($_.Name)"}else{"(blank)"}}},
Count | Format-Table -AutoSize
# Examine a single guest in detail
Write-Host ""
Write-Host "Sample guest (first accepted guest):"
$sampleGuest = $guests | Where-Object { $_.ExternalUserState -eq 'Accepted' } |
Select-Object -First 1
if ($sampleGuest) {
$sampleGuest | Select-Object DisplayName, UserPrincipalName, Mail,
ExternalUserState, CreationType, CreatedDateTime, CompanyName,
@{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}},
@{N='HomeTenant';E={
if ($_.UserPrincipalName -match '#EXT#') {
($_.UserPrincipalName -split '_' | Select-Object -Last 1) -replace '#EXT#@.*',''
}
}}
}
} else {
Write-Host "No guest users — your tenant has no external trust relationships."
Write-Host "This is common for developer tenants and new organizations."
Write-Host "In production, guests accumulate through SharePoint sharing,"
Write-Host "Teams guest access, and B2B invitations."
}How to interpret the guest breakdown:
ExternalUserState: Accepted — the guest completed the invitation and can authenticate. These are your active external trust relationships.
ExternalUserState: PendingAcceptance — invitation sent but never completed. The identity object exists with whatever access was intended, but the external user never authenticated. These are stale trust relationships — potential attack surface if the guest's email is compromised.
ExternalUserState: (blank) — the guest was created through implicit sharing (someone shared a SharePoint document or Teams channel with an external email), not through a formal B2B invitation. Nobody deliberately decided to grant this access — a sharing action created the trust relationship automatically.
CreationType: Invitation — confirms deliberate B2B invitation. CreationType: null with UserType: Guest — created through implicit sharing, not a deliberate invitation.
The #EXT# in the UPN encodes the guest's home identity. The format is user_hometenant.com#EXT#@yourtenant.com. You can extract the home tenant domain from this to understand which external organizations have trust relationships with your tenant.
Reading your results:
If guest count = 0, you have no external trust relationships. This is normal for developer tenants. In production, even small organizations accumulate guests through SharePoint sharing.
If most guests are Accepted, your guest population is active. Check LastSignIn for each — accepted guests who haven't signed in for 180+ days are stale.
If many guests are PendingAcceptance, invitations are being sent but not completed. Each pending invitation is an unused trust relationship. Delete pending invitations older than 90 days.
If many guests have blank state, people are sharing documents with external email addresses, creating guest identities implicitly. This often indicates external sharing permissions are too broad.
The ratio of guests to members matters. Guests at 10–20% of total users is typical for collaborative organizations. Guests at 50%+ suggests the tenant is heavily used for external collaboration and needs dedicated guest governance (MSA12).
Check your guest count and status breakdown. PendingAcceptance guests older than 90 days are invitations that were never accepted — they should be revoked. Blank-state guests were created through implicit sharing (someone shared a file with an external email). Every guest is an external trust relationship. If you have no cross-tenant access policies and no guest access review process, your guest population is ungoverned.
Architectural significance: Guest identities are architecturally complex for three reasons. First, authentication happens in a tenant you don't control — you can't enforce password policies, MFA methods, or device compliance on the guest's home tenant. Second, CA policy evaluation is split — your resource-tenant policies evaluate the sign-in, but the guest's home-tenant policies also apply via cross-tenant access settings. Third, the default guest permissions in Entra ID are more permissive than most architects realise. Unless explicitly restricted, guest users can enumerate the directory, list other users, and query group memberships.
Entra Admin Center
Identity → Users → All users → filter User type to Guest
The portal shows guest accounts with their invitation status. Click any guest → Properties to see ExternalUserState and CreationType. The portal doesn't show `signInActivity` — use the Graph API query for staleness analysis.
Category 4 — Workload identities
Workload identities represent non-human actors — applications, automation scripts, background services, and third-party integrations. In Entra ID, a workload identity consists of two linked objects that serve different purposes.
An application registration (also called an app registration) is the definition — a template that describes the application's identity. It specifies the application's name, its authentication configuration (client secrets, certificates, or federated credentials), and the API permissions it requests. The application registration is a global object — it exists in the tenant where it was registered and can be referenced by other tenants if the application is configured as multi-tenant.
A service principal is the local instance — the tenant-specific representation of the application. When an application is used in a tenant, Entra ID creates (or the admin creates) a service principal for it. The service principal is what actually receives permissions, appears in sign-in logs, and can be targeted by CA policies. Think of the application registration as the blueprint and the service principal as the building constructed from that blueprint in a specific location.
Connect-MgGraph -Scopes "Application.Read.All"
# Inventory app registrations in YOUR tenant
$apps = Get-MgApplication -All -Property DisplayName, AppId, CreatedDateTime,
PasswordCredentials, KeyCredentials
Write-Host "Application registrations: $($apps.Count)"
if ($apps.Count -gt 0) {
# Analyse credential age — the most critical security property
$credAnalysis = $apps | ForEach-Object {
$app = $_
$_.PasswordCredentials | ForEach-Object {
[PSCustomObject]@{
App = $app.DisplayName
Type = 'Secret'
Created = $_.StartDateTime
Expires = $_.EndDateTime
AgeDays = [int]((Get-Date).ToUniversalTime() - $_.StartDateTime).TotalDays
Expired = $_.EndDateTime -lt (Get-Date).ToUniversalTime()
}
}
$_.KeyCredentials | ForEach-Object {
[PSCustomObject]@{
App = $app.DisplayName
Type = 'Certificate'
Created = $_.StartDateTime
Expires = $_.EndDateTime
AgeDays = [int]((Get-Date).ToUniversalTime() - $_.StartDateTime).TotalDays
Expired = $_.EndDateTime -lt (Get-Date).ToUniversalTime()
}
}
}
$totalCreds = $credAnalysis.Count
$expiredCreds = ($credAnalysis | Where-Object { $_.Expired }).Count
Write-Host "Total credentials: $totalCreds"
Write-Host "Expired credentials: $expiredCreds"
Write-Host ""
# Show oldest/most concerning
$credAnalysis | Sort-Object AgeDays -Descending |
Select-Object App, Type, @{N='Created';E={$_.Created.ToString("yyyy-MM-dd")}},
@{N='Expires';E={$_.Expires.ToString("yyyy-MM-dd")}}, AgeDays, Expired -First 10 |
Format-Table -AutoSize
} else {
Write-Host "No application registrations found."
Write-Host "In a developer tenant, this is normal — you haven't registered any apps yet."
Write-Host "In production, most M365 tenants have 10-100+ app registrations for"
Write-Host "integrations, automation, and third-party SaaS connectors."
}How to interpret the credential analysis:
Expired credentials don't automatically disable the application — the app registration persists. Expired credentials mean the credential can no longer obtain new tokens, but previously issued long-lived tokens may still be valid. More importantly, expired credentials indicate nobody is managing this application. An app with credentials that expired a year ago is either broken (nobody noticed) or authenticating through a different mechanism.
Multiple credentials on one app (e.g., two secrets) usually means someone created a new secret when the old one was about to expire but never deleted the old one. This is a sign of manual, reactive credential management.
Apps with zero credentials may be using managed identity authentication (the safest pattern), delegated-only permissions (no app-owns-data scenarios), or they may be abandoned registrations that were never configured.
The ratio of app registrations to service principals reveals how many registrations have corresponding tenant instances. Run this to check:
$sps = (Get-MgServicePrincipal -All `
-Filter "servicePrincipalType eq 'Application'").Count
Write-Host "Service principals (application type): $sps"
Write-Host "App registrations: $($apps.Count)"
if ($apps.Count -gt $sps) {
Write-Host "Gap: $($apps.Count - $sps) registrations without service principals"
Write-Host "These may be multi-tenant apps, abandoned registrations,"
Write-Host "or apps that were registered but never fully configured."
}Check your app registration count and credential expiry status. Expired credentials indicate a reactive rotation pattern — credentials are only rotated when the application breaks. App registrations without corresponding service principals are unused registrations that should be cleaned up. Without Workload ID Premium, every app registration and service principal bypasses CA policies entirely — this is typically the highest-priority identity gap in the tenant.
Architectural significance: Workload identities are the fastest-growing attack vector. Research consistently indicates that over 60% of cloud breaches involve compromised non-human identities. The reasons are structural: application registrations accumulate permissions that nobody reviews, client secrets are long-lived and rarely rotated, workload identities don't support traditional MFA, and standard CA policies don't evaluate workload identity sign-ins. A separate CA policy targeting workload identities requires Workload ID Premium licensing ($3/workload identity/month). Without it, every service principal authenticates outside the CA framework.
Category 5 — Managed identities
Managed identities are a special type of service principal where Azure manages the credentials automatically. You don't create secrets or certificates — Azure issues and rotates tokens internally. The token is obtained from the Azure Instance Metadata Service (IMDS), accessible only from within the Azure resource that the managed identity is assigned to.
$managedIds = Get-MgServicePrincipal -All `
-Filter "servicePrincipalType eq 'ManagedIdentity'" `
-Property DisplayName, ServicePrincipalType, AppId, AlternativeNames
Write-Host "Managed identities: $($managedIds.Count)"
if ($managedIds.Count -gt 0) {
$managedIds | Select-Object DisplayName, ServicePrincipalType,
@{N='ResourceType';E={
($_.AlternativeNames | Where-Object { $_ -match '/providers/' }) -replace '.*/providers/','' -replace '/.*',''
}} | Format-Table -AutoSize
} else {
Write-Host "No managed identities found."
Write-Host "This is expected if your tenant has no Azure resources"
Write-Host "(VMs, App Services, Functions, Logic Apps, Sentinel workspace)."
Write-Host "Managed identities are the safest authentication pattern for"
Write-Host "Azure workloads — any Azure service that authenticates to"
Write-Host "Entra ID should use a managed identity instead of a client secret."
}If you see managed identities, that is the architecturally ideal pattern — each is tied to a specific Azure resource with no credentials to manage. As you expand Sentinel (MSA8) and automation (MSA10), managed identities should be mandatory for all new Azure service authentication.
Architectural significance: Managed identities are the safest identity type because they eliminate the credential problem that makes workload identities dangerous. The architectural principle: any Azure workload that needs to authenticate to Entra ID or Azure resources should use a managed identity. If an application uses a client secret instead, the ADR should document why.
Your identity census
Combine all five categories. Run this script to produce your complete census:
Write-Host "========================================="
Write-Host " IDENTITY CENSUS — YOUR TENANT"
Write-Host "========================================="
Write-Host ""
$cloudOnly = (Get-MgUser -All -Filter "userType eq 'Member'" `
-ConsistencyLevel eventual |
Where-Object { $_.OnPremisesSyncEnabled -ne $true }).Count
$synced = (Get-MgUser -All -Filter "onPremisesSyncEnabled eq true" `
-ConsistencyLevel eventual).Count
$guests = (Get-MgUser -All -Filter "userType eq 'Guest'").Count
$apps = (Get-MgApplication -All).Count
$sps = (Get-MgServicePrincipal -All `
-Filter "servicePrincipalType eq 'Application'").Count
$managed = (Get-MgServicePrincipal -All `
-Filter "servicePrincipalType eq 'ManagedIdentity'").Count
$total = $cloudOnly + $synced + $guests + $apps + $sps + $managed
$userCount = $cloudOnly + $synced
Write-Host "Cloud-only members: $cloudOnly"
Write-Host "Synced members: $synced"
Write-Host "Guest users: $guests"
Write-Host "App registrations: $apps"
Write-Host "Service principals: $sps"
Write-Host "Managed identities: $managed"
Write-Host "---"
Write-Host "TOTAL identities: $total"
Write-Host "'User' count: $userCount"
Write-Host "Hidden identities: $($total - $userCount) (guests + apps + SPs + managed)"
Write-Host ""
if ($total -gt $userCount) {
$pct = [math]::Round(($total - $userCount) / $total * 100, 1)
Write-Host "$pct% of your identity population is NOT in the default Users view."
Write-Host "These are the identities most security architectures miss."
}Interpreting your census — what the numbers tell you about your architecture:
Compare your total to your "user" count. The gap is the identities your admin center's default view hides. In every tenant we've assessed, the gap is between 10% and 40% of the total population. These hidden identities — guests, app registrations, service principals — are the ones most likely to be unmanaged.
If your tenant is a clean developer tenant (12 users, 0 guests, 0 app registrations), you'll see a small census with no hidden identities. That's expected. The lab baseline (MSA1.12) adds the NE structure so you can practise the full assessment process.
If your tenant is a production tenant, the census reveals the real architectural surface. Pay attention to the guest-to-member ratio (above 20% suggests heavy external collaboration), the app registration count (above 50 suggests significant automation or SaaS integration), and any expired credentials (immediate risk).
Compare your total identity count against your "user" count. The gap — guests, app registrations, service principals, managed identities — represents the identity population that your existing CA policies, lifecycle management, and monitoring likely don't cover. This census is the starting point for every design decision in the rest of the course.
A CA policy targeting "All Users" with "Require multifactor authentication." The architect believes this covers the tenant. It doesn't. "All Users" includes member users (cloud-only and synced) and guest users — but it does not include workload identities. Application registrations and service principals authenticate through the client credentials flow, which bypasses all user-targeted CA policies. These identities operate with no MFA, no device compliance check, no risk evaluation, and no location restriction. A single leaked client secret gives the attacker whatever permissions the application holds — and the CA framework never evaluates the authentication. This gap exists in every tenant without Workload ID Premium licensing — regardless of size.
Before moving on, verify your understanding: Run the identity census script against your tenant. What is your total identity count vs your "user" count? What percentage of your identities are hidden from the default admin center view? If both numbers are the same, explain why (hint: developer tenants with no guests or apps). Look at your synced user count. If it's zero, explain what this means for your identity architecture (which MSA1 topics don't apply to you?). If it's non-zero, check the sync age — is your sync infrastructure healthy?
Reusable script — the commands from this sub assembled for operational use:
Save your census output into your architecture package at 01-identity/identity-census.md. Include both the raw numbers and your interpretation — what the census tells you about your specific environment's architectural profile (cloud-only vs hybrid, guest-heavy vs internal-only, app-heavy vs minimal automation).
This census is the input for every subsequent design decision. MSA1.3 uses it to scope the threat model per identity type. MSA3 uses it to determine which CA policies target which populations. MSA4 uses it to scope privileged access management. MSA9 uses it to determine which sign-in logs to monitor. MSA12 uses it to scope lifecycle management and access reviews.
You're reading the free modules of m365-security-architecture
The full course continues with advanced topics, production detection rules, worked investigation scenarios, and deployable artifacts.