Conditional Access Policy Change Notifier
Monitor Azure AD conditional access policy changes and alert the security team in Teams with before/after details.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
The Conditional Access Policy Change Notifier is a daily-scheduled cloud flow that watches every Microsoft Entra ID (Azure AD) Conditional Access policy in the tenant and alerts the security team in Microsoft Teams and over email whenever a policy is created or modified. A SharePoint list (FlowLibs - CA Policy Snapshots) acts as the historical store — each detected change writes a per-policy snapshot row capturing the full policy JSON, the previous and current state, and a human-readable change summary.
This flow is purpose-built for IT Admins and security operations teams that need an out-of-band audit trail of CA policy drift — independent of the Entra ID admin center's own audit log retention window — and want immediate, in-channel notification when a policy is altered.
Use Case
In any production tenant, Conditional Access is the primary identity perimeter. Silent or accidental modification of a CA policy (toggling state, broadening included users, weakening grant controls) can dramatically expand the blast radius of a credential compromise. This flow runs once every 24 hours against the Microsoft Graph /identity/conditionalAccess/policies endpoint, compares each policy's serialized snapshot to the most recent snapshot persisted in SharePoint, and detects three states: NEW (policy did not exist last run), MODIFIED (snapshot JSON differs), and NO_CHANGE (silent skip). For every NEW or MODIFIED policy it writes a new history row to the snapshot list, posts an HTML-formatted alert card to a Teams channel, and sends a high-importance email summarizing the change, the previous state, the current state, and the Graph-reported modifiedDateTime.
The flow ships in the Off state. Activation requires only (a) authorizing the SharePoint, Teams, and Outlook connections, (b) populating the Graph app-registration credentials inline on the HTTP action, and (c) updating environment variable values for the target Teams group / channel / recipient email.
The flow is ideal for teams that:
- Runs once every 24 hours against the /identity/conditionalAccess/policies Microsoft Graph endpoint
- Compares each policy's serialized snapshot to the most recent snapshot persisted in SharePoint
- Detects three states: NEW (policy did not exist last run), MODIFIED (snapshot JSON differs), and NO_CHANGE (silent skip)
- For every NEW or MODIFIED policy: writes a new history row, posts a Teams alert card, and sends a high-importance email
Flow Architecture
Check For CA Policy Changes Daily
RecurrenceFires every 24 hours. Hard-coded interval; the `flowlibs_CACheckIntervalHours` env var documents the intent.
Init varGraphApiEndpoint
Initialize variableReads `flowlibs_GraphApiEndpoint` env var (default `https://graph.microsoft.com/v1.0`).
Init varSharePointSiteUrl
Initialize variableReads `flowlibs_SharePointSiteURL` env var.
Init varSnapshotListName
Initialize variableReads `flowlibs_CASnapshotListName` env var (`FlowLibs - CA Policy Snapshots`).
Init varTeamsGroupId
Initialize variableReads `flowlibs_TeamsGroupId` env var.
Init varTeamsChannelId
Initialize variableReads `flowlibs_TeamsChannelId` env var.
Init varRecipientEmail
Initialize variableReads `flowlibs_RecipientEmail` env var.
Get Conditional Access Policies
HTTP (built-in)`GET {GraphApiEndpoint}/identity/conditionalAccess/policies` with `ActiveDirectoryOAuth` (client-credentials). Returns the full set of CA policies in the tenant.
Parse Policies Response
Parse JSONSchema-extracts `value[]` with `id`, `displayName`, `state`, `createdDateTime`, `modifiedDateTime`.
Get Existing Snapshots
SharePoint - GetItemsEnvironment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_GraphApiEndpoint | String | https://graph.microsoft.com/v1.0 | Base URL for Graph calls. Override for sovereign clouds (e.g. `https://graph.microsoft.us/v1.0`). |
| flowlibs_SharePointSiteURL | String | https://your-tenant.sharepoint.com | Site that hosts the snapshot list. |
| flowlibs_CASnapshotListName | String | FlowLibs - CA Policy Snapshots | Title of the snapshot list. List must exist before activation. |
| flowlibs_TeamsGroupId | String | <configure> | Group ID (a.k.a. team ID) of the Teams team that owns the alert channel. |
| flowlibs_TeamsChannelId | String | <configure> | Channel ID inside the team where alerts are posted. |
| flowlibs_RecipientEmail | String | alerts@yourcompany.com | UPN or distribution list address that receives email alerts. |
| flowlibs_CACheckIntervalHours | String | 24 | Documentation-only — the Recurrence trigger requires a static integer; this var documents the intent for ops. Keep in sync with the trigger if changing cadence. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| SharePoint | shared_sharepointonline | GetItems (Fetches existing snapshot rows for change comparison) PostItem (Writes a new snapshot history row when a change is detected) |
| Microsoft Teams | shared_teams | PostMessageToConversation (Posts the HTML alert card to the configured channel) |
| Office 365 Outlook | shared_office365 | SendEmailV2 (Sends the high-importance security alert email) |
| HTTP | built-in | HTTP GET (Calls Graph `/identity/conditionalAccess/policies` with ActiveDirectoryOAuth — the Conditional Access API is not exposed by the `shared_azuread` connector, so HTTP is required) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Create the SharePoint snapshot list
- Create FlowLibs - CA Policy Snapshots (Generic List, BaseTemplate 100) with columns Title, Policy_x0020_ID (Text), Policy_x0020_Name (Text), Policy_x0020_State (Choice: enabled / disabled / enabledForReportingButNotEnforced), Snapshot_x0020_JSON (Note), Last_x0020_Checked (DateTime), Change_x0020_Detected (Boolean), Change_x0020_Details (Note). The flow references these internal names directly, so they must match exactly.
- Register a Microsoft Entra ID app
- Create an app registration with Policy.Read.ConditionalAccess (or broader Policy.Read.All) Application permission, grant admin consent, and capture the tenant ID, client ID, and client secret for the HTTP action.
- Populate the HTTP action credentials
- Edit the Get Conditional Access Policies HTTP action and replace the inline placeholders REPLACE_WITH_TENANT_ID, REPLACE_WITH_CLIENT_ID, REPLACE_WITH_CLIENT_SECRET with real values. Best practice in production: switch the auth type to Key Vault-backed credentials before activation rather than embedding the secret in the flow definition.
- Update environment variable values
- Set flowlibs_TeamsGroupId, flowlibs_TeamsChannelId, and flowlibs_RecipientEmail to the target audience. Override flowlibs_GraphApiEndpoint for sovereign clouds. Confirm flowlibs_SharePointSiteURL matches the site that hosts the snapshot list.
- Authorize connection references and re-bind SharePoint dropdowns
- Open each connection reference (SharePoint, Teams, Outlook) and sign in with a service account that has rights to the snapshot list, the Teams channel, and the sending mailbox. After authorization, open Get Existing Snapshots and Create Snapshot Item and reselect the site/list from the dropdowns — the API-deployed bindings sometimes require this in the new designer.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Change Type resolver
Collapses the three-state outcome into a single scalar that the downstream Condition can branch on with simple equals checks. `empty()` covers the NEW case (no prior history row); `equals()` on the serialized JSON covers MODIFIED-vs-NO_CHANGE.
EXPR.02Filter previous snapshot
Filter array `where` clause. `items('For_Each_Current_Policy')` references the outer Foreach's current iteration; `item()` references the inner Filter's current iteration. The Filter array action's Query type accepts the slash-path SharePoint internal column name.
EXPR.03Coalesce on optional Graph fields
Graph's `modifiedDateTime` can be absent on freshly created policies; `coalesce` produces a safe fallback string for the email subject and body without throwing a null-reference error in concat.
EXPR.04Snapshot serialization
`string()` on the policy object produces a stable, deterministic representation suitable for equality comparison across runs. Object key ordering is preserved by the Logic Apps runtime, so two structurally identical policies produce byte-identical strings.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.