An exclusion opens one gate. The account has to pass all three.
Every Microsoft 365 tenant should have accounts that still work when everything else has failed: when the federation provider is down, the MFA service is unreachable, or a Conditional Access change has locked out every administrator. Microsoft calls them emergency access accounts. Most teams call them break-glass accounts, and most teams build them the same way: a cloud-only account, Global Administrator, a long password in a safe, and an exclusion from every Conditional Access policy so nothing can stop it signing in.
That last step is where the design goes wrong. The exclusion removes the account from your policies. It doesn't remove it from Microsoft's. Since October 2024, Microsoft has required multifactor authentication for sign-ins to its admin portals, and that requirement is enforced by the service, not by a policy you wrote. An account that holds only a password, excluded from everything you control, can still be refused at the one door it exists to open.
This post covers what decides whether a break-glass account works when you need it: the method it signs in with, the policies it is actually excluded from, and the alerts that tell you when it was used or changed. Each comes with a check you can run today.
The requirement no exclusion reaches
Microsoft's mandatory MFA arrived in two phases. Phase 1 covers the Azure portal, the Microsoft Entra admin center and the Intune admin center for any create, read, update or delete operation. Enforcement began in October 2024 and the Microsoft 365 admin center was added in February 2025. Phase 2 extends the requirement to Azure CLI, Azure PowerShell, the Azure mobile app, infrastructure-as-code tools and the control-plane REST API, for create, update and delete operations, starting 1 October 2025.
There is no opt-out. Tenants could postpone each phase for a while (Phase 1 until 30 September 2025, Phase 2 until 1 July 2026), and both dates have now passed. Microsoft's FAQ is explicit that the requirement covers every user account, emergency access accounts included.
Three controls, and only one takes an exclusion
Three different things can demand MFA from an emergency access account, and they are easy to run together:
- Conditional Access is yours. You write the policies, and you decide who they exclude.
- Security defaults is Microsoft's preset for tenants without Conditional Access: on or off, no per-user exclusions beyond the directory synchronization accounts, and MFA for Global Administrators at every sign-in. A tenant running it has no break-glass exclusion to configure, and must turn it off before using Conditional Access.
- Mandatory MFA is enforced by Microsoft on the admin portals and management tools, whichever of the other two you run, and nothing you configure removes it.
An exclusion only ever answered the first of the three.
The method that has to work
The question, then, is whether the account holds a method that satisfies Microsoft's requirement on its own, with nothing else working. Microsoft recommends two for these accounts: a passkey (FIDO2) or certificate-based authentication, and says both satisfy the mandatory MFA requirement. It adds conditions that are easy to miss:
- A different method from your other administrators. If the break-glass accounts use Microsoft Authenticator like every other admin, one outage locks out all of them at once.
- A method that doesn't expire or get cleaned up. An unused key or certificate can fall into an automated cleanup of stale methods, leaving only the password.
- A credential the right people can reach. Microsoft's guidance is storage in separate secure locations and a designated secure workstation for the sign-in. Two keys in one drawer fail together.
The benchmarks
Microsoft's guidance for emergency access accounts: at least two accounts, cloud-only on the onmicrosoft.com domain, Global Administrator assigned permanent active rather than eligible in PIM, an alert on every sign-in at severity 0, and validation at least every 90 days. Entra keeps sign-in logs for 30 days on P1 and P2 tenants and 7 on Free, so a 90-day validation record needs the logs sent somewhere that keeps them longer.
The first check is an inventory, and it takes one call per account. This counts the FIDO2 keys registered on each, using the Microsoft Graph PowerShell SDK:
# Inventory: does each emergency access account have a passkey (FIDO2) registered?
# A registered key is not a working key. The drill below is the test of that.
Connect-MgGraph -Scopes 'UserAuthenticationMethod.Read.All' -NoWelcome
$breakGlass = @(
'00000000-0000-0000-0000-000000000001', # object IDs, not UPNs
'00000000-0000-0000-0000-000000000002'
)
foreach ($id in $breakGlass) {
$keys = @(Get-MgUserAuthenticationFido2Method -UserId $id)
[pscustomobject]@{
Account = $id
Fido2Keys = $keys.Count
Verdict = if ($keys.Count -gt 0) { 'FIDO2 method registered' }
else { 'No registered FIDO2 method; verify alternative authentication' }
}
}A registered key isn't a working key: the output says nothing about where it is kept, whether the person on call can reach it, or whether it works from their workstation. A zero doesn't prove the account is password-only either, because certificate-based authentication isn't registered as a method and this check can't see it. A zero means finding out what the account would use instead, and only the drill below settles that.
The policies the account is actually excluded from
Microsoft's guidance is to exclude the emergency access accounts from every Conditional Access policy that blocks or restricts sign-in, and to do it through a dedicated group rather than account by account. Report-only policies don't block anything and don't need the exclusion.
The exclusion is right on the day it is set up. What breaks it is the next policy. Someone creates a new policy six months later, targets all users, and forgets the group, and nobody notices until the night the break-glass account meets that policy at sign-in. Screen every enabled policy at every review. The script lists enabled policies with no explicit exclusion for the group or both accounts. Each row is a candidate to investigate, not proof the policy applies:
# Screening: enabled policies with no explicit exclusion for the emergency access
# group or both accounts. Each row is a candidate, confirmed by reading the policy.
Connect-MgGraph -Scopes 'Policy.Read.All' -NoWelcome
$bgGroup = '00000000-0000-0000-0000-0000000000aa' # the EmergencyAccess group
$bgUsers = @('00000000-0000-0000-0000-000000000001',
'00000000-0000-0000-0000-000000000002')
Get-MgIdentityConditionalAccessPolicy -All |
Where-Object { $_.State -eq 'enabled' } | # report-only needs no exclusion
ForEach-Object {
$users = $_.Conditions.Users
$groupOut = $users.ExcludeGroups -contains $bgGroup
$missing = @($bgUsers | Where-Object { $users.ExcludeUsers -notcontains $_ })
if (-not $groupOut -and $missing.Count -gt 0) {
[pscustomobject]@{
Policy = $_.DisplayName
Includes = ($users.IncludeUsers + $users.IncludeRoles + $users.IncludeGroups) -join ', '
Grant = ($_.GrantControls.BuiltInControls -join ', ')
}
}
}The script reads assignments only, so it errs both ways. A policy scoped to a group the accounts aren't in is listed and harmless. A policy that excludes the group passes, yet still reaches an account quietly removed from that group, because the script never checks membership. For each row, confirm membership, then read includes, exclusions and grant controls together: one that includes All users or the Global Administrator role without the exclusion is the lockout you were trying to prevent. Run it after every Conditional Access change.
The group is a control of its own
Excluding through a group means anyone who can add a member to it can exempt any account from every policy you have, such as a helpdesk role that manages groups or an application with broad group permissions.
A role-assignable group is optional hardening for this, not a requirement for the exclusion to work. By default only Privileged Role Administrators and the group's owners can change its membership, membership must be assigned rather than dynamic, and a Graph application holding Group.ReadWrite.All can't change it; that needs RoleManagement.ReadWrite.Directory. The setting can't be added to an existing group, so adopting it means a new group, with the governance a privileged group brings. Either way, its membership belongs on the watch list below.
Watch what changes on the account
Someone who wants to use a break-glass account later doesn't have to sign in with it today. They can register their own method on it, reset its password, take it out of the exclusion group, or change the policies around it. Each of those leaves an audit record, and each should reach a person:
// Hypothesis: the emergency access accounts, their exclusion group and the
// Conditional Access policies change only through a reviewed change.
let BgGroup = "00000000-0000-0000-0000-0000000000aa";
let BgUsers = dynamic(["00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002"]);
AuditLogs
| where OperationName in (
"Add member to group", "Remove member from group", // the exclusion
"User registered security info", "User deleted security info",
"Admin registered security info", "Admin updated security info",
"Admin deleted security info", // methods
"Reset password", "Update user", "Disable account", // credential, status
"Add member to role", "Remove member from role", // privilege
"Add Conditional Access policy", "Update Conditional Access policy",
"Delete Conditional Access policy") // the policies
| where TargetResources has BgGroup
or TargetResources has_any (BgUsers)
or OperationName endswith "Conditional Access policy"
| project TimeGenerated, OperationName, Result, InitiatedBy, TargetResourcesEvery Conditional Access change is in scope, because a new policy that forgets the exclusion is what the screening script exists to catch.
The sign-in that should never be routine
An emergency access account with no sign-ins is the healthy state. Every sign-in is either a drill, a real emergency or misuse, and Microsoft's guidance is to alert on every one, at the highest severity, so that a person decides which it was. That includes failures: a failed sign-in on a break-glass account is someone trying it. The query follows Microsoft's own example, matching on object ID, with the columns an investigator opens first:
// Hypothesis: an emergency access account signs in only during a declared
// emergency or an announced drill. Every other attempt, failed or not, is an incident.
let BreakGlass = dynamic(["00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002"]);
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(15m) // reaches back past ingestion delay
| where ingestion_time() > ago(5m) // each event alerts once, in the first run that sees it
| where UserId in (BreakGlass) // object IDs survive a UPN change; names don't
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress,
ResultType, ResultDescription, ConditionalAccessStatus,
AuthenticationRequirement, AuthenticationDetails, CorrelationId, TypeRun it as an analytics rule every five minutes with a fifteen-minute lookback, alerting on more than zero results. Sign-in events reach the workspace minutes late, so a window that matches the schedule can miss one. Widening the window alone alerts twice on one sign-in; the ingestion_time() line gives each event to the first run that sees it, which is Microsoft's own pattern for delayed data. Near-real-time rules are the alternative.
Two columns are easy to over-read. ConditionalAccessStatus is a starting point, not proof an exclusion is missing: Microsoft records success when a policy was evaluated for the sign-in, even one that then didn't apply, so open the sign-in's Conditional Access results to see which policy touched the account and why. AuthenticationRequirement records what was required, not what was used, and reads singleFactorAuthentication when primary authentication failed before MFA was evaluated. The methods used, and whether MFA was satisfied, are in AuthenticationDetails.
The same rule in Sigma, for teams on another SIEM:
title: Emergency Access Account Sign-In
id: a281dc56-ceca-4f66-8538-96fafefaa1a6
status: experimental
description: Any sign-in by a named emergency access account. Every use is reviewed by a person.
references:
- https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access
author: Ridgeline Cyber
date: 2026-10-20
tags:
- attack.initial-access
- attack.t1078.004
logsource:
product: azure
service: signinlogs
detection:
selection:
UserPrincipalName:
- 'bg-01@yourtenant.onmicrosoft.com'
- 'bg-02@yourtenant.onmicrosoft.com'
condition: selection
falsepositives:
- Validation drills, which should be announced before they start
level: criticalField names depend on your backend's pipeline, so check the converted query against a real event. And it matches on the name, so renaming the accounts blinds it.
Proving it works
The checks above tell you the account should work. Only a sign-in proves it does. Microsoft's guidance is to validate the accounts at least every 90 days, and again whenever IT staff change or subscriptions change, and the validation is more than "can it sign in":
- Sign in to the Microsoft Entra admin center with each account, using its passkey or certificate, and perform one administrative read.
- Confirm the alert fired and reached a person who acknowledged it.
- Confirm the list of people allowed to use the accounts is current, and that the credentials and safe combinations changed when anyone on it left.
- Confirm nobody has registered a personal phone or personal email for MFA or password reset on the accounts.
The sign-in side of those drills can be checked from the logs. The query starts from the list of accounts rather than from the sign-ins, so an account with no successful sign-in still gets a row, marked as the failure it is:
// Hypothesis: each account signed in successfully within the last 90 days.
// An account with no success in the window is reported, not silently dropped.
let BreakGlass = datatable(UserId:string) [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002"];
let LastSignIn = SigninLogs
| where TimeGenerated >= ago(120d)
| where ResultType == "0"
| summarize LastSuccess = max(TimeGenerated) by UserId;
BreakGlass
| join kind=leftouter LastSignIn on UserId
| extend DaysSinceLastSuccess = datetime_diff("day", now(), LastSuccess)
| extend Status = case(isnull(LastSuccess), "NO SUCCESSFUL SIGN-IN FOUND",
DaysSinceLastSuccess > 90, "OVERDUE",
"WITHIN 90 DAYS")
| project UserId, LastSuccess, DaysSinceLastSuccess, StatusA successful sign-in isn't a successful drill, so keep the drill record too. And on a workspace that keeps 90 days, an account last tested 100 days ago and one never tested both read NO SUCCESSFUL SIGN-IN FOUND.
Where to read more
- Microsoft Learn, Manage emergency access admin accounts: accounts, methods, exclusion, monitoring and validation.
- Microsoft Learn, Mandatory Microsoft Entra multifactor authentication: the phases, and why break-glass accounts are not exempt.
- Microsoft Learn, Security defaults and Sign-in log activity details: what each control requires, and what the Conditional Access and authentication fields record.
- Microsoft Graph, the
conditionalAccessUsersresource, read by the exclusion screen. - SigmaHQ, the
rules/cloud/azure/signin_logsfolder, for other Entra sign-in rules. - MITRE ATT&CK, T1078.004 Valid Accounts: Cloud Accounts.
What to do this week
- Run the method inventory. Any emergency access account with no registered passkey needs a known alternative, tested. Register a FIDO2 key that no other administrator's method depends on.
- Run the exclusion screen and investigate every row it returns: confirm group membership, then fix any enabled policy that includes all users or the Global Administrator role without excluding the emergency access group.
- Deploy the sign-in alert and the audit alert at the highest severity, keyed on object IDs, including failed sign-ins, routed to a person rather than a shared mailbox.
- Schedule the first drill within 90 days, announce it, and record the result.
- Send sign-in and audit logs to a workspace that keeps them longer than 90 days, so the drill record outlives Entra's own 30.
An emergency access account is the one design decision you hope never to rely on. That is exactly why it is the one most likely to fail silently, because nothing uses it often enough to show that it no longer works.
Free preview: Conditional Access Design
How the evaluation engine decides every sign-in, and how scope, conditions and controls combine into one verdict.
Read the free section →