Microsoft have now added the ability to restore Conditional Access Policies which have been deleted in Microsoft Entra. This is really useful for inadvertent deletion scenarios.
In this blog, we will explore how to restore a deleted policy, and why this is an important (and welcome!) change from Microsoft.
How to restore a policy
- Within Entra, navigate to Security > Conditional Access.
- On the left hand menu, you will then see Deleted Policies.

- Within here, you will be able to see the Deleted Date/Time, the Permanent deletion date, as well as the user who deleted the policy.

- Selecting the three dots to the right of the entry, you can either delete permanently or restore your policy.

- You will then see a prompt to restore, or to restore into report only mode.

Why this is important
This soft deletion stage now allows us to quickly restore policies.
Conditional Access is used to seriously reduce the attack surface of an organisation through stringent controls. Deletion of these (either accidental or nefarious) can prevent very serious risks. The ability to quickly restore these without the need to rebuild them from scratch could potentially be a very useful time saver for businesses.
Protecting your Conditional Access
Needless to say, it is important that we take steps to protect our conditional access policies. One such way we can do this is by reviewing and alerting on log ingestion within Sentinel. For example:
User added to Conditional Access Policy Exclusion Group
AuditLogs
| where OperationName == "Add member to group"
| extend Type = tostring(TargetResources[0].type)
| where Type == "User"
| extend ['Group Name'] = tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[1].newValue)))
| extend UserAdded = tostring(TargetResources[0].userPrincipalName)
| where isnotempty(UserAdded)
| where ['Group Name'] contains "CAP-EXCL-"
| project TimeGenerated, Group=['Group Name'], UserAdded, InitiatedBy
| sort by Group asc, TimeGenerated descConditional Access Policy deleted
AuditLogs
| where Category == "Policy"
| where OperationName has_any ("Delete conditional access policy", "Delete Conditional Access Policy", "Delete policy")
| extend PolicyName = tostring(TargetResources[0].displayName)
| extend PolicyId = tostring(TargetResources[0].id)
| project
TimeGenerated,
InitiatedBy = tostring(InitiatedBy.user.userPrincipalName),
InitiatedByApp = tostring(InitiatedBy.app.displayName),
OperationName,
PolicyName,
PolicyId,
Result = tostring(Result),
ActivityDateTime,
CorrelationId,
AADTenantId,
IPAddress = tostring(InitiatedBy.user.ipAddress),
AdditionalDetails = tostring(AdditionalDetails)
| order by TimeGenerated descConditional Access Policy state modification
AuditLogs
| where Category == "Policy"
| where OperationName has_any ("Update conditional access policy", "Update Conditional Access Policy")
| extend PolicyName = tostring(TargetResources[0].displayName)
| extend ModifiedProperties = TargetResources[0].modifiedProperties
| mv-expand ModifiedProperties
| extend PropertyName = tostring(ModifiedProperties.displayName),
OldValue = tostring(ModifiedProperties.oldValue),
NewValue = tostring(ModifiedProperties.newValue)
| where PropertyName in~ ("state", "State", "conditions.state", "grantControls.state", "sessionControls.state")
| where NewValue =~ "reportOnly" or NewValue =~ "disabled" or NewValue =~ "off"
| project
TimeGenerated,
OperationName,
PolicyName,
PropertyName,
OldValue,
NewValue,
InitiatedBy = tostring(InitiatedBy.user.userPrincipalName),
InitiatedByApp = tostring(InitiatedBy.app.displayName),
Result = tostring(Result),
IPAddress = tostring(InitiatedBy.user.ipAddress),
CorrelationId,
AADTenantId
| order by TimeGenerated desc