Graph Visualization of External Teams Collaborations in Azure Sentinel
Published May 03 2020 10:17 PM 8,313 Views



In the recently published Remote Work Trend Report, Microsoft provided some interesting insights into how the shift into full-time remote work and learning has impacted the way we collaborate and remain productive with internal and external stakeholders. On the flip side, cybersecurity teams now have a mammoth task of securing virtual meetings, ensuring the right policies and permissions are being implemented, and monitoring guest access to meetings and data.


Expanded View.png

The Rationale


By using graphs, we can visualize interactions between:

  • Internal users  (light blue nodes)

Making it easier to visually identify outliers and smaller clusters that diverge from typical enterprise behavior.

  • Large clusters imply that many enterprise users are interacting with the external domain 
  • Small clusters imply few or stray enterprise user interactions with the external domain.
  • Size of the edge (ie. thickness of the arrow) represents the frequency of interaction between an individual enterprise user and the external domain. So a thicker arrow would imply more frequent interactions between a specific user and the external domain.





Implementation Guide



  • [link] Setup Azure Sentinel
  • [link] Enable Office365 Audit Logging (Teams activity is a subset)

Leveraging the good work of other contributors (in the links above) we can dive straight into creating the graph visualization in Azure Sentinel workbooks.


Workbooks and Graph Visualization

Once in Azure Sentinel, navigate to workbooks, and add a query module. You will be prompted to enter a KQL query, and a visualization drop down list will allow you to toggle how the query results will be presented - in this case we will be selecting Graph.



Preparing the KQL Query

:warning: Note: Since Teams data is being retrieved through Logic Apps [link] if you did not define the table as TeamsData, just replace any mention of TeamsData in the following section to the table name you defined.


1. Teams data preparation

In the initial data datatable, the Members string which are user emails (internal and external) gets parsed to split the domain from the username. Internal organization domains then get filtered out (since we are only concerned about external domains in this scenario). A RequestId is then formed by stringing the external domain together with the requester (internal user).




let CallerOrg = (TeamsData
| extend InternalOrg = tostring(split(UserId, "@")[1])
| distinct InternalOrg);
let data = (TeamsData
| extend UPN = tostring(parse_json(Members)[0].UPN) 
| extend Organization = tostring(split(UPN, "@")[1]) 
| where isnotempty(Organization)
| summarize Calls = count() by ExtDomain = Organization, Request = UserId, Dependency = UPN
| where ExtDomain !in (CallerOrg)
| extend RequestId = strcat(ExtDomain, '::', Request)); 





2. Building a list of the links

The links datatable then groups the number of unique calls/interactions between the internal user and an external domain, and we append the type of interaction in the “Kind” column. 




let links = data
| summarize Calls = sum(Calls) by ExtDomain, RequestId
| project SourceId = ExtDomain, TargetId = RequestId, Calls, Kind = 'ExtDomain -> Request';





3. Building a list of nodes

The nodes datatable counts and groups the total number of calls/interactions being made to each external domain, and again we append the type of interaction in the “Kind” column. We also union and combine the number of calls/interactions being initiated by internal users and append the type “Request”.




let nodes = data
| summarize Calls = sum(Calls) by ExtDomain
| project Id = ExtDomain, Name = ExtDomain, Calls, Kind = 'ExtDomain'
| union (data
    | summarize Calls = sum(Calls) by RequestId, Request
    | project Id = RequestId, Name = Request, Calls, Kind = 'Request');





4. Putting it all together - combining the nodes and links

The final step is combining the nodes and links datatables into a single datatable.




| union (links)
| extend HEXColour = iif(Kind == "Request", "cad8e6", "28496b")





5. Optional

In graphs you can specify the color you would like the nodes to appear in. Just append the desired HEX color in a separate column — in this example I made internal users appear light blue, and external domains appear dark blue. If you would like, you could also add additional layers of color coding to enrich the visualization. Eg. assigning different colors to different internal user groups (HR could be yellow, Finance could be red, etc.)


Graph Layout Settings

Clicking into Graph Settings, a small pop-out window will appear on the right. The layout settings are where you define what each node will represent. Do note this is highly dependent on how you have structured the KQL query above, in this example by using:

  • Node ID = Id

This will give you a view where the central node is the external domain and the surrounding nodes are the internal users.



Save the query and the workbook and start to explore the different clusters and interactions.




Regular Visitor

Hi Sharonko,


It's a really good idea to visualize Teams usage with Sentinel. We're looking at reports with Public vs. Private users but with this kind of diagrams, it's quite clear and more useful -don't need to analyze lines or cells. We'll take a look at implementing for other case scenarios.


Thank you,


Jean Luc 

Occasional Contributor

@sharonko Thanks for posting this article. I tried this integration and i some issues with this


1. Every log getting generated in the O365 portal is getting ingested in log analytics workspace. like Sharepoint logs, one drive(I'm already collecting these logs using O365 connector) , flows, stream, and many other logs but I need only logs related to Teams through this integration. Any way to get this achieved 


2. In my case, i don't see the external org name in the UPN attribute. It's giving a long string that doesn't have the "@" and domain name.  How I can fix this issue. 


@Pavan_Gelli1910 thanks for the comments!


1/ Teams data is not yet available through the native Office365 connector you're using. For now, to bring Teams data in you'll need to use a Logic App to pull it <link here


2/ Once you bring in the Teams data the UPN will be filled in with the right data!

Occasional Contributor



Thank you for your response.


For point 1. I have already done the custom integration and the logs are getting ingested into log analytics and I also see the Teams logs in the LA. But the below issue still exists. 


In my case, I don't see the external org name in the UPN attribute. It's giving a long string that doesn't have the "@" and domain name. 


How I can fix this issue?

Occasional Contributor

Hi all,


Anyone has done this graph-visualization-of-external-teams-collaborations on sentinel workbook.? If yes, can you please help me with how you have done this.


Thank You.

Occasional Contributor

Can someone please advise where I can find the KQL query to generate the TeamsData info when I'm using the native Office365 Sentinel connector and not a logic app.


As I understand it the process is to create a function that generates theO365API_CL table and then save that as a function called TeamsData. My question is where do I find the syntax of the KQL query to generate the O365API_CL table in the first place? 


i've been going around in circles for two days and I'm starting to feel very stupid... :) Can anyone assist?

Occasional Contributor

@PeterJNGL  If your using the native Office 365 connector then the teams data will be available under this table 


| where OfficeWorkload == "MicrosoftTeams"


I didnt understand your second query. Fyi, If your doing custom integration i.e. logic app to get additional teams data then the table name is defined during the creation.





Occasional Contributor

The documentation indicates that if you are using the native connector you don't need the Logic app.. Based on that could i create a newtable called TeamsData using 

| where OfficeWorkload == "MicrosoftTeams"

Version history
Last update:
‎Nov 02 2021 05:53 PM
Updated by: