Ingesting Office 365 Alerts with Graph Security API

Published 11-05-2019 11:25 AM 10.7K Views

Ingesting Office 365 Alerts with Graph Security API

During recent Azure Sentinel workshops some customers have asked for the possibility to ingest Office 365 alerts into Azure Sentinel. While Azure Sentinel has Office 365 Connector, this connector  ingests Exchange mailbox audit logs and SharePoint audit logs and as such it doesn’t include Office 365 alerts.


With Office 365 alerts administrators can be alerted about anomalous or malicious activity in their Office 365 environment, for example malware campaign detection or suspicious email forwarding. To learn more about Office 365 alerts you can refer to Alerts in the Office 365 Security & Compliance Center. Administrators can also define their custom alerts in Office 365 Security & Compliance Center.


While Office 365 alerts connector may be be released in future, in the meantime we can leverage Graph Security API to ingest Office 365 alerts into Azure Sentinel. Also, as this approach is based on Graph Security API, you can use it to get alerts from other Microsoft Security Products that support Graph Security API and don't have Azure Sentinel alerts connectors released yet.



1.   Using Microsoft Graph Security API to read Office 365 Alerts

As with most Microsoft security products, you can access Office 365 alerts through Microsoft Graph Security API. This API provides restful access to Microsoft security alerts. To further understand possible queries via Graph Security API you can review sample queries in github repository.

To test out Graph queries we will use Microsoft Graph API Explorer. Before running security alerts queries, please ensure you have at least minimum permission in the API Explorer to read security alerts. You should have SecurityEvents.ReadAll as minimum. To check/add your permissions click on modify permissions link on left side of Graph Explorer.


Once we have the right permission, we need to form a query to retrieve Office 365 alerts. Let’s start with initial query for all alerts:


As we are interested to retrieve only Office 365 alert, we will apply following filter that we put into Graph Explorer:

/security/alerts?$filter=vendorInformation/provider eq 'Office 365 Security and Compliance' and category eq 'ThreatManagement'



In the Response section please note lastModifiedDateTime field, this is the datetime of when alert was created/modified in Office 365. We will use this field later to retrieve only alerts since this datetime.


2.      Ingesting alerts

Once we retrieve the list of Office 365 alerts through Graph Security API, we will ingest them into Azure Sentinel. We will be using Azure Sentinel Playbook. As we can’t ingest directly into SecurityAlerts table, we will be ingesting into custom logs Office365Alerts_CL table. Our playbook will be running at scheduled interval (e.g. every 5 mins). 


In the playbook logic we will first check for the most recent lastModifiedDateTime in the Office365Alerts_CL table and then retrieve only new alerts since that datetime. If the table is empty or doesn't exist, we will retrieve all alerts from Office 365 (this is the initialization phase).


Now, let’s have a look at each step in more details.


3.      Creating Azure Sentinel Playbook

You can create new playbook in your Azure Sentinel environment, in the Playbooks section.


Once the playbook is created, add Recurrence function from the list of available functions and set recurrence to your defined time, e.g. every 5 mins:



4.      Retrieving the most recent lastModifiedDateTime.

Now, we will be looking for the latest alert in Office365Alerts_CL table and the datetime of when it was modified/created. As mentioned earlier, this information is populated by Office 365 and is stored in lastModifiedDateTime field. If there are no alerts in Office365Alerts_CL table or table doesn’t exist, we will retrieve all Office365 alerts and initialize the table.


Let's put together corresponding KQL Query. First, we need to check, if Office365Alerts_CL already exists. As there’s no built-in function in KQL to check for table existence, we will use union and isfuzzy=true operator. If isfuzzy is set to true, the set of union sources is reduced to the set of table references that exist and are accessible at the time. If at least one such table is found, it will produce warning, but query will still execute. The default value is false, meaning that any query against non-existing table will yield an error.


1. We will be doing union with new oldDateTime variable that will contain only one record, which is historical date (set to 1st of January 1900). We don’t expect to have any alerts generated before this date.


This is how we define the oldDateTime variable:



 let oldDateTime = view () { print lastModifiedDateTime_t=datetime("1900-01-01 00:00:00") };




2. After we define the variable, we can execute the union function. We will be joining oldDateTime with Office365Alerts_CL table. We will use arg_max function to get the most recent lastModifiedDateTime_t value.




Office365Alerts_CL |  summarize arg_max(lastModifiedDateTime_t, lastModifiedDateTime_t)  | project lastModifiedDateTime_t




And this is how the final query looks like – note we added one more arg_max function that compares oldDateTime we defined earlier (Step 1) and the latest lastModifiedDateTime in Office 365Alerts_CL table. If there are no alerts in Office365Alerts_CL table, the query will just return oldDateTime value.




let oldDateTime = view () { print lastModifiedDateTime_t=datetime("1900-01-01 00:00:00") };
union isfuzzy=true
(Office365Alerts_CL |  summarize arg_max(lastModifiedDateTime_t , lastModifiedDateTime_t )  | project lastModifiedDateTime_t )
| summarize arg_max(lastModifiedDateTime_t , lastModifiedDateTime_t )
| project lastModifiedDateTime_t   




5.      Execute Query in the Playbook

To execute previous query in Playbook against Sentinel Log Analytics workspace, we will add Azure Log Analytics Action into the playbook:



Now, we can add our query into Azure Log Analytics action:




6.      Using Get alerts Action

Once we have the filter expression, we can run Graph API query to get the list of Office 365 Alerts. Azure Sentinel Playbook comes with Microsoft Graph Security action (currently in preview) that allows to easily run Graph Security API queries.


First, let’s add Microsoft Graph Security API action into our Playbook:



And now we will look for GetAlerts function:



Next, enable filtering on Get alerts action:


Now, add the Graph Security API query to retrieve the list of Office 365 Alerts that we have created in Step 1 and include datetime filter as below. Please, don’t forget to add space into “Filter alerts” box after adding lastModifiedDateTime_t variable from the list of dynamic variables.


This is the final Graph Security API query:



And now added into Playbook action:



7.      Ingest Office 365 alerts into Azure Sentinel

As a final step, we will ingest Office 365 alerts that we retrieved in previous step into Office365Alerts_CL table. We will do so by adding Azure Log Analytics Send Data action into our playbook.

Before doing so, we first add For each action that will iterate through all Office 365 Alerts received through Graph Security API in the previous step



Now we add Send Data action from Azure Log Analytics Data Collector



And now we can ingest alerts into source log table which is Office365Alerts – note that you will have two CurrentItem items – please ensure you select the one that is associated to the Alerts iteration.


8.      Summary

To test the playbook, we can execute it by clicking on Run Trigger and selecting Recurrence in Playbook page


Once the playbook execution is completed, we can check for alerts by running query in Azure Sentinel Logs:



And we are done. In this article we have demonstrated how to use Graph Security API to ingest Office 365 Alerts into custom table in Azure Sentinel. We have built Azure Sentinel playbook and leveraged new Graph Security API action to retrieve Office 365 alerts and ingest them into Azure Sentinel Custom Logs table. As a next step we can for example translate these alerts into Sentinel incidents through custom alert rules. 


For your reference, this is the final playbook:


Super Contributor

The link Microsoft Security Products that support Graph Security API  appears to only take you to the Bing homepage.


Thanks @Gary Bushey , the link has been fixed

Senior Member

Thanks for taking the time to put this together.


As per "Step 5", it seems there have been some changes to the "Run Query and List Results" step.


There is now a "Time Range" box.


My setup appears to be ingesting duplicate alerts from 365 Sec and Compliance Centre


Any chance you're able to test on your side and confirm if any changes need to be made to the logic app please?


Hi @security_maverick , yes, I've noticed these changes, I'm looking into it now. Thanks for flagging. 

Senior Member

"Run Query and List Results" for Azure Log Analytics connector is depreciated now. I was trying to create this playbook but could not find this. After some researching, I found about it.  Looks like time management of alerts will need a new way now.


Hi @ihsmktier_00 where did you find the run query has been deprecated please? I'm not aware of it. There's just been released new version of run query than now includes also time range for the query.

Occasional Contributor

Hi @Stefan Simon ,


Brilliant article! Very detailed and the easiest example I have seen to pull events from the ISG.





Frequent Visitor

Hi @security_maverick , yes, I've noticed these changes, I'm looking into it now. Thanks for flagging.


I'm having duplicate data, did you find how to avoid it?

Thank you

Senior Member


I am still waiting on feedback from Stefan.

Senior Member

@Stefan Simon 

Here, Azure Log Analytics connector does not list any actions.





Senior Member

@ihsmktier_00  - check for Azure Monitor instead of Azure Log Analytics - you'll find it there

Senior Member

Best practice on the this in production is to monitor the successful running of your LogicApp / Playbook as well - if the app fails for example you really need to know about it!


One issue with this is around the Graph version of the alert not always having all the data in it  - malicious links alerting for example - doesn't actually have the user in it - we need to go looking in the portal for this. There is a lot of different alerts that need to be catered for / parsed individually in the "create an alert in sentinel" to have something that can entity link with other alerts and incidents.


Is there any sign of an official connector being ready?


@Stefan Simon 


What permissions do i need to use Microsoft Graph Api through Playbook - Logic APP as fas as i understand there is a user connection so i must delegate that user the permissions to Microsoft Graph Api ?


Any example article is welcome

New Contributor


@Stefan Simon 

Thank you for this info. Is there any update on the Time Range to be specified in the run query and list results action?

Occasional Contributor

@Stefan Simon , 

I`m not able to find the categories Office 365 ... and ThreatManagement.

When I run the query to display all alerts I see alerts from ASC and Sentinel but nothing related to Office365.  I see the malware in Quarantine in the O365 portal I can`t grab this via API Calls. 




Senior Member

@Stefan Simon 
There is possibility to use logic app for patch alerts for : provider  Office 365 Security and Compliance ?

Senior Member

@Stefan Simon 
There is possibility to use logic app for get Air investigation evidence/status and pool into Sentinel ?

Occasional Visitor

@Stefan Simon 
Hi Stefan, I was able to recreate this playbook, thanks to your detailed steps . However, every time the playbook runs, it fetches all the data again on my Office365 security and alerts account, and thus flooding my sentinel logs with duplicates. Anything should I change on the current playbook? 

Occasional Contributor

Hi @Stefan Simon ,
Thank you for this post.
Can you please upload the JSON template of this playbook?

Version history
Last update:
‎Nov 11 2019 08:18 AM
Updated by: