# Access Control Lists for Graph Data Items

In Part 1, we synchronized OutSystems application data with Microsoft Search & Intelligence using the Microsoft Graph External Data Connections API. This, along with a search vertical and result display template, made our data available to users in the search console.

During synchronization, we gave permission to the ingested data records to the "everyone" group, allowing all organization users to search and view the data. In a real application, you will likely need to restrict who can search and view the ingested data. In this tutorial, we will explore how to manage access control lists for ingested data records to ensure that only authorized users have access.

# **Demo Application**

This article series includes a demo application called "**ODC with Graph Demo**," available on **ODC Forge**. Be sure to download the version of the application that matches this article.

For this article, you need to install Version 0.2 from ODC Forge.

* In the ODC Portal, go to **Forge - All Assets**.
    
* Search for "**ODC with Graph Demo**".
    
* Click on the Asset **(Do not click on Install on the tile!)**.
    
* Switch to the **Version** tab and click on Install next to **Version 0.2**.
    

Version 0.2 depends on other Forge components:

* **OAuthTokenExchange** - An external logic library that helps retrieve access tokens easily. In this tutorial however we use an action to retrieve the discovery document from Microsoft Entra only.
    
* **Graph External Data Connections API** - A connector library that integrates with Microsoft Graph API for external data connections.
    
* **Graph Users API** - A connector library that integrates with Microsoft Graph API users endpoint to retrieve user details.
    

# What we build

In this part of the tutorial series, we will add specific access control list entries to ingested testimonials, allowing access only to designated users. This involves the following steps:

* **Mapping User Accounts** - Map OutSystems application users to Microsoft Entra users.
    
* **Access Control List** - Add access control list entries during data ingestion to Microsoft Graph.
    

# Prerequisites

Before we begin, please reset the existing external data connection configurations in your Microsoft 365 tenant by following the [cleanup steps](https://without.systems/ingesting-outsystems-data-into-microsoft-graph#heading-cleanup) and recreating the external data connection schema as described in part one of the series.

## Entra Application Permissions

Our application registration we are using to ingest data requires one additional permission to retrieve Entra user details. In the [**Entra admin center**](https://entra.microsoft.com/), [go to **Application**](https://entra.microsoft.com/)**s - App Registrations** and select the “Graph External Data Connection ODC” registration.

In the **API permissions** menu, click **Add permission**.

* Select **Microsoft Graph** and **Application permission**.
    
* Search for and select **User.ReadBasic.All** permission.
    
* Click **Add permission**.
    

This permission requires an administrator to grant admin consent.

* Click **Grant admin consent &lt;your domain&gt;** and confirm the dialog.
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Instead of <strong>User.ReadBasic.All</strong>, you can also select<strong> User.Read.All</strong> permission, which grants access to all user details, whereas <strong>ReadBasic </strong>only allows retrieval of limited user attributes. However, for the demo, this is sufficient.</div>
</div>

Before we try the demo application, let's explore Access Control List entries for external data in Microsoft Graph a bit more in depth.

# Access Control List Entries

Access Control List entries in Microsoft Graph for external data specify who can access certain ingested data records and the level of access they have.

Entries are defined by a **type** attribute that identifies the identity you want to grant permissions to. The type can have one of the following values:

* **user** - Indicates that you want to grant permission to an individual Entra user account.
    
* **group** - Indicates that you want to grant permission to an individual Entra group.
    
* **externalGroup** - A non-Entra group that your application manages. We will discuss external groups shortly.
    
* **everyone** - A special type that indicates public access.
    
* **application** - Grants permission to a specific Entra application registration.
    

Next, a **value** must be provided that corresponds to the type you specified:

* **user** - The Entra user's object identifier.
    
* **group** - The Entra group's object identifier.
    
* **externalGroup** - The external group identifier.
    
* **everyone** - Must be set to “everyone”.
    
* **application** - The Application (client) ID of the Entra application registration.
    

Finally, the **accessType** attribute defines the level of permission:

* **grant** - Grants access to the record.
    
* **deny** - Denies access to the record. Deny Access Control List entries take precedence over grant entries.
    

A sample JSON representation of a single Access Control List entry that grants a specific Entra user access to a record looks like this.

```json
{
  "accessType": "grant",
  "type": "user",
  "value": "381a61cf-7d4e-40a3-a225-83c5bc36fbf0"
}
```

A Graph ingested data record can have multiple Access Control List entries with different types.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">It's important to note that Access Control List entries must always be included in the <strong>Graph_CreateOrUpdateItem</strong> operation, even when you're just updating an item. There is no separate way of managing ACLs for items.</div>
</div>

# Mapping User Accounts

Microsoft 365 users are authenticated through Microsoft Entra, and Entra does not automatically recognize OutSystems user accounts. Therefore, to manage authorizations, we need to identify which Entra user account matches an OutSystems user.

Even if OutSystems users log in through Entra, we, as of today, cannot directly access the login identification data within our application. The only information we have is the user data from the **User** entity. This means we have to use the **user's email address** to identify the corresponding Entra user.

There are two ways to do this. If the email address of an OutSystems user matches the **Universal Principal Name (UPN)** of the Entra user, we can easily get the Entra user details using the **Graph\_GetUser** action from the **Graph Users API** connector library.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">In Microsoft Entra, the <strong>User Principal Name (UPN)</strong> is the unique sign-in name for a user and is formatted like an email address.</div>
</div>

If the OutSystems user's email address does not match Entra's UPN, you can use the **Graph\_ListUsers** action from the **Graph Users API** connector library. This allows you to filter for the user whose mail attribute matches the OutSystems user's email address. The filter would look like this

```plaintext
mail eq 'mail@domain.com'
```

Read more on [OData filters](https://learn.microsoft.com/en-us/graph/filter-query-parameter) in the Microsoft Documentation.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The demo application includes a test screen where you can try out <strong>filter </strong>and <strong>search </strong>criteria.</div>
</div>

# External Groups

External Groups represent group entities from systems outside of Microsoft Entra. These groups are used to reflect the access control structures of external data sources.

When developing an OutSystems application with record-level permissions (Entity records), it's better to manage permissions at the group or role level instead of individual user accounts. This makes permission management easier over time.

External Groups for Microsoft Graph External Data let you sync your application's group or role structures and use them in Access Control List entries for item permissions.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Syncing here means <strong>you need to manage the group</strong>. It is your responsibility to add and remove external group members whenever there is a change in your application.</div>
</div>

An external group is identified by a unique identifier that you need to specify when creating an external group in Microsoft Graph. This identifier can be any alphanumeric string without special characters. The external identifier is then used in all future member management operations.

The **Graph External Data Connections API** connector library offers the actions you need to create, update, and delete external groups, as well as add and remove group members.

## Creating an External Group

With the **Graph\_CreateExternalGroup** action you can create a new external group in Microsoft Graph by providing the following information.

* **ExternalGroupId** - The unique identifier of the external group. Can be any alphanumeric string without special characters.
    
* **DisplayName** - Optional display name
    
* **Description** - Optional description
    

## Adding and Removing External Group Members

The actions **Graph\_CreateIdentity** and **Graph\_DeleteIdentity** let you add and likewise remove members from an external group. Members can be one of the following types

* **user** - A Microsoft Entra user account identified by its object identifier.
    
* **group** - A Microsoft Entra group identified by its object identifier.
    
* **externalGroup** - Another, already existing, external group that you want to nest.
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Please note that it may take up to 30 seconds after creating a new external group before you can add members to it.</div>
</div>

## Adding an External Group Item Access Control List Entry

Using an external group in an ACL is straight forward. The JSON representation of an external group ACL would look like this.

```json
{
  "accessType": "grant",
  "type": "externalGroup",
  "value": "externalGroupIdentifier"
}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">When adding an external data item to Microsoft Graph with ACLs based on new external groups, it may take some time for Microsoft 365 to evaluate group memberships. From my own observations up to 60 minutes.</div>
</div>

Now that we have covered the basics, let's explore the implementation of the demo application.

# **Demo Application Walkthrough**

In this walkthrough, we will explore the key implementation details. Be sure to check the comments in the various server actions as well.

The demo application assigns Access Control List entries based only on user accounts, without using external groups. As a small challenge, try implementing a group permission structure in the demo application on your own.

In **ODC Studio**, open the **ODC with Graph** Demo application.

## Saving a Testimonial

In the **Logic** tab, open the **SaveTestimonial** action under **Server Actions - Actions**. This action runs when a user creates or updates a testimonial on the **Testimonial** screen.

This action first performs a check if it is a new testimonial or an existing one and depending on the outcome executed either the **Testimonial\_Create** or **Testimonial\_Update** CRUD wrappers.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748863405157/73c97498-b9a9-4a85-8587-7e8a71acea25.png align="center")

The **AddPermission** server action is executed when creating a new and is responsible for creating the initial record permission for the creator of the testimonial.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748862343882/b1a8d7b5-10a4-4195-8711-8ce486726556.png align="center")

* **GetUserDetails** - Reads the current user's email from the User entity.
    
* **GetEntraAccessToken** - Retrieves an access token using the application credentials to access Microsoft Graph.
    
* **Graph\_GetUser** - Queries Microsoft Graph using the user's email address to obtain Entra user details with the Universal Principal Name.
    
* **TestimonialMember\_Create** - A CRUD wrapper to create a record in the TestimonialMember entity with the OutSystems User Identifier and the Entra user's object identifier.
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If your OutSystems user email address does not match your Entra Universal Principal Name, you should replace <strong>Graph_GetUser</strong> with <strong>Graph_ListUsers</strong> and use filter criteria to find the correct Entra user.</div>
</div>

Back in the **SaveTestimonial** action, whether creating or updating a testimonial, the **IngestContent** finally syncs the testimonial with Microsoft Graph.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748862727928/71fd2190-c865-4846-9bbc-5830baee5807.png align="center")

Most of the action flow was already explained in part 1. The new addition is the **GetTestimonialAccessList** action, which retrieves the current permissions from the **TestimonialMember** entity and returns a list of **ItemAccessControlList** and assigned to the **Graph\_CreateOrUpdateItem** request structure.

## Modifying Permissions

You can modify testimonial permissions in the demo application for existing testimonials. In the **Permissions** tab of a testimonal you can add or remove OutSystems users.

Adding a new member with permissions to a testimonial triggers the **AddTestimonialMember** action under **Server Actions - Actions**. Removing a member triggers the **RemoveTestimonialMember** action.

Both actions first execute the **AddPermission** or **DeletePermission** actions, followed by the **IngestContent** action to update the record with the new ACL entries in Microsoft Graph.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748864120344/de2e5405-e6fa-4836-8297-d471b10cfee4.png align="center")

This wraps up the demo application walkthrough. Try using the demo application with different user accounts to see the search results in Microsoft 365. As an extra challenge, try adding external group management to the application.

# Notes and Recommendations

The demo application performs all actions synchronously, triggered from the frontend. In a real use case, you should offload data ingestion to Microsoft Graph to a workflow.

In addition, I recommend implementing a queue that stores an entry for each data item (testimonial) to create, update, and delete, and removes the entry from the queue once the data ingestion operation succeeds. This way, you minimize the risk of application data and graph data becoming inconsistent.

# Summary

In this tutorial, we explored how to define Access Control List entries for ingested data items to limit access for specific user accounts. We also discussed how the Graph External Data Connections API and Graph Users API connector libraries can be used to manage external groups and their memberships.

I hope you enjoyed it and would appreciate your feedback.

%%[follow-linkedin-button]
