Automating Custom Email Alerts From Entra ID Audit and Other Logs


This document provides a technical guide for implementing customized HTML email notifications for Azure Monitor Log Analytics using Azure Logic Apps, System-Assigned Managed Identities, and the Microsoft Graph API.

This worked example will alert you when a user registers a new MFA method on their account. This is the sort of functionality that you get with Cisco DUO MFA but not with Entra ID (out of the box, so to speak). The MFA activity is recorded in your Entra ID Audit logs, and so a query against these, which need to be configured to be copied to an Azure Log Analytics workspace, can be used to trigger alerts via Azure Monitor. In Azure Monitor you schedule a query to run every x minutes (30 in this example) and triggers a Logic App to run on demand when the query sees results from the audit log. The Logic App queries the same Log Analytics workspace to retrieve the audit log data and then packages that into a HTML table which it places in an email body and then sends an email from a shared mailbox. The Logic App runs as a system managed identity, and the managed identity has Mail.Send permissions to just this shared mailbox using RBAC for Applications in Exchange Online.

1. Logic App Workflow Configuration

The execution pipeline relies on an Azure Logic App triggered via HTTP payload from Azure Monitor. Once triggered, the workflow extracts the alerting parameters, queries the Log Analytics workspace using Kusto Query Language (KQL), constructs a formatted HTML body, and dispatches the email using Microsoft Graph.

1.1 Create the Logic App

This is the application that is called when the alert fires. This contains the logic to process the query and fire the email off to the recipients. This is only triggered by the alert firing.

Create this Logic App in Azure in a subscription of your choice. Create a Logic App (Consumption) and this logic app needs to have its own Managed Identity before you start. This identity is used to be granted permissions to the different resources we need to access (log analytics and mailbox). In the Logic App page, click Settings > Identity and enable the Status to “On” under System Assigned. A managed identity will be created in Entra for the name of this Logic App.

Click the Azure Role Assignments button to grant the permissions to the system assigned managed identity. For querying Log Analytics it will need Log Analytics Data Reader to the Log Analytics workspace that contains the data you are alerting on. Therefore select the Resource Group that contains the workspace and grant this permission. Also grant the Reader permission to the subscription.

1.2 HTTP Trigger Configuration

In the Logic App Designer view click + and select When an HTTP request is received as the trigger. Set the JSON Schema payload to capture the standard Azure Monitor Common Alert Schema.

  • Method: Post
  • Request Body JSON Schema:
{
  "type": "object",
  "properties": {
    "schemaId": { "type": "string" },
    "data": {
      "type": "object",
      "properties": {
        "essentials": {
          "type": "object",
          "properties": {
            "alertRule": { "type": "string" },
            "severity": { "type": "string" },
            "signalType": { "type": "string" },
            "monitorCondition": { "type": "string" },
            "alertTargetIDs": {
              "type": "array",
              "items": { "type": "string" }
            }
          }
        },
        "alertContext": {
          "type": "object",
          "properties": {
            "SearchIntervalStartTimeUtc": { "type": "string" },
            "SearchIntervalEndtimeUtc": { "type": "string" }
          }
        }
      }
    }
  }
}

1.3 Azure Monitor Logs Action (KQL Execution)

Add the Azure Monitor Logs – Run query and list results action to retrieve detailed logs for the alert window. Into this action in the Logic App Designer we will add the query that you ran to trigger this Logic App in the first place. Often the alert does not send the results of the query to the logic app, and so by running the query when the Logic App is triggered allows us to paste the results into the email alert. Indeed by running a more advanced query than that which we ran to trigger the Logic App we can provide more customization in the email notification.

If the connection is invalid, change the Managed Identity value to System Assigned Managed Identity at the bottom of the fly-out.

  • Resource Type: Log Analytics Workspace
  • Subscription: <Select Subscription>
  • Resource Group: <Select Resource Group>
  • Resource Name: <Select Log Analytics Workspace>
  • Query:
AuditLogs
| where TimeGenerated >= ago(35m)
| where OperationName in ("User registered security info", "User changed object", "Admin updated security info")
// Extract and rename the columns
| extend ['Username'] = tostring(TargetResources[0].userPrincipalName),
        ['Initiated By'] = tostring(InitiatedBy.user.userPrincipalName),
        Time = TimeGenerated
// Keep only the most recent event for each target user
| summarize arg_max(Time, ['Initiated By'], Result) by ['Username']
// Reorder to match your desired output
| project ['Username'], ['Initiated By'], Time, Result 
  • Time Range: This can be either set in the query (in which case ensure that the value shown in the drop-down is greater, or data will be truncated, or specified here. For values not available in the drop down, set them in the query such as “| where TimeGenerated >= ago(30m) ”

2. Formatting the Email Content

To transform raw JSON results into an executive-ready report, raw outputs are parsed into a native tabular structure and styled with inline CSS within an HTML shell.

2.1 Create HTML Table Action

  1. Add the Data Operation Create HTML table action.
  2. From: Click the lighting bolt icon. Select the `value` array output from the Run query and list results action: `body(‘Run_query_and_list_results’)?[‘value’]`
  3. Under Advanced, select **Columns**: Automatic. We will update the query later to make for tidy column names.

2.2 Compose HTML Document with CSS

  1. Add a Compose action (rename this to `Compose Email Body`) to embed the table within an HTML layout.
  2. Enter the following HTML. This contains the HTML structure of the email without needing to paste it directly into the JSON of the next step, which will be complex. Change the title and any of the body to suit your requirements:
&lt;style>
 table { border-collapse: collapse; width: 100%; font-family: 'Segoe UI', Arial, sans-serif; font-size: 13px; }
 th { background-color: #0078D4; color: #ffffff; text-align: left; padding: 8px 12px; border: 1px solid #005A9E; }
 td { border: 1px solid #e1e1e1; padding: 8px 12px; }
 tr:nth-child(even) { background-color: #f8f9fa; }
 tr:hover { background-color: #f1f1f1; }
&lt;/style>

&lt;h3>Audit Event Log Summary&lt;/h3>
&lt;p>An Entra ID Audit Log alert has been triggered for security info or object modifications.&lt;/p>

body('Create_HTML_table')

&lt;p>The above user(s) have modified their MFA settings in the last 30 minutes.&lt;/p>

We will come back and finish off the Logic App shortly. First we need to create and set the correct permissions on the Alerts mailbox to allow the Logic App to send email from the Alerts mailbox

3. Sending Emails via Microsoft Graph API Using a Managed Identity

To avoid stored secrets or user credentials, dispatch notifications using the Microsoft Graph API authenticated with a System-Assigned Managed Identity.

3.1 Provisioning & RBAC Permissions

  1. Collect the generated Identity Object ID (Principal ID) of the System Assigned Managed Identity from the Settings > Identity page.
  2. Assign the Graph API permission Application Mail.Send using Exchange Online RBAC for Applications via PowerShell:
$TenantID = "&lt;Your-Tenant-ID>"
$LogicAppObjectID = "&lt;Logic-App-Managed-Identity-Object-ID>"

Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline

The console outputs two distinct GUIDs for your Logic App. Keep these handy.

$mi = Get-MgServicePrincipal -ServicePrincipalId  $LogicAppObjectID
Write-Host "AppId: $($mi.AppId) | ObjectId: $($mi.Id)" 

Then in Exchange Online, a new service principle is created that can be assigned permissions to send email from the mailbox. The name of the service principal is set at the end of the line, change to suit your naming.

New-ServicePrincipal -AppId $mi.AppId -ObjectId $mi.Id -DisplayName "LogicApp-Alerts-MI"

Create a Custom Management Scope that scopes the permission that you will set in the following cmdlet to just this mailbox. If you want to allow multiple mailboxes, then create a distribution group that your mailboxes are members of and change the restriction filter to MemberOfGroup (see https://c7solutions.com/2024/09/secure-access-to-mailboxes-via-graph)

New-ManagementScope -Name "LogicApp-Alerts-Scope" -RecipientRestrictionFilter "PrimarySmtpAddress -eq 'alerts@yourdomain.com'"

Assign the role to the service principal

New-ManagementRoleAssignment -Name "LogicApp-Send-AlertsMailbox" -App $mi.AppId -Role "Application Mail.Send" -CustomResourceScope "LogicApp-Alerts-Scope"

3.2 Graph API HTTP Action Configuration

Add an HTTP action to the Logic App canvas configured as follows (change the email address to your alerts mailbox in the first line). The message body is added as the email content via a dynamic content reference to the previous stage in the Logic App (call “Compose Email Body”).

  • Name: Change “HTTP” to something like “Send Email via Graph”
  • URI: https://graph.microsoft.com/v1.0/users/alerts@yourdomain.com/sendMail
  • Method: POST
  • Headers:
    • Content-Type: application/json
  • Body: (the “bit in bold”outputs” section inside “content” needs replacing with a matching Dynamic Content from Logic Apps)
{
 "message": {
   "subject": "Alert: End User MFA Created",
   "body": {
     "contentType": "HTML",
     "content": "outputs('Compose_Email_Body')"
   },
   "toRecipients": [
     {
       "emailAddress": {
         "address": "recipient@domain.com"
       }
     }
   ]
 },
 "saveToSentItems": "false"
} 
  • Advanced > Authentication:
    • Authentication Type: Managed Identity
    • Managed Identity: System-assigned managed identity
    • Audience: https://graph.microsoft.com

If you need to send the email to more than one recipient, either enter a distribution group or use the following code example, with each separate recipient entered with a comma at the end of each one apart from the last one.

{
 "message": {
   "subject": "Alert: Subject",
   "body": {
     "contentType": "HTML",
     "content": "outputs('Compose_Email_Body')"
   },
   "toRecipients": [
     {
       "emailAddress": {
         "address": "one@domain.com"
       }
     },
     {
       "emailAddress": {
         "address": "two@domain.com"
       }
     },
     {
       "emailAddress": {
         "address": "three@otherdomain.com"
       }
     }
   ]
 },
 "saveToSentItems": "false"
} 

4. Azure Monitor Alert Rule Configuration

To route alerts to the Logic App:

  1. Navigate to Azure Monitor > Alerts > Create > Alert Rules.
  2. Scope: Target the primary Log Analytics Workspace.
  3. Condition: Define a custom log search signal using KQL (e.g., checking for MFA registration, and you can use the same one that is used in the Logic App).
  4. Actions:
    1. Create or select an Action Group.
    2. Under Action Type, select Logic App.
    3. Select your configured Logic App workflow.
    4. Enable the Common Alert Schema toggle (Enable Common Alert Schema = Yes).
  5. Details: Configure Alert Rule Name, Severity (Sev 0 to Sev 4), and evaluation frequency.
  6. Click Review + Create to deploy the rule.

You are now done. When the activity that you are querying for occurs in the Audit log (or indeed any log that you are shipping to Azure Log Analytics), the Logic App is triggered, and that app queries the same Log Analytics Database to get the detail for the email. It can be as simple as rendering a table as we do here, or more complex with conditions and other logic.

The result being a Graph Mail.Send call and permissions to send as a single mailbox.


Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.