Single Azure AD tenant for large enterprises, part 6: B2B sync engine in details
In this part I will explain configuration of Logic Apps based B2B sync engine described in the previous part in more details
Authentication
Sync engine contains several Azure resources and uses Graph API to communicate with Azure AD. Depending on type and location (local or remote tenant) different authentication types are used.
1. User Managed Identity
- Graph API for local (destination) tenant.
OAuth2 token audience for Graph API is https://graph.microsoft.com
- Key Vault
OAuth2 token audience for Key Vault REST API is https://vault.azure.net
2. OAuth2 with service principal’s secret
Azure AD authentication with service principal is used to authenticate to remote tenant with OAuth2
3. API connection (shared key)
For simplicity queries to Azure Storage tables are performed not via REST API but using built-in connector. This connector doesn’t allow to use managed identity for authentication.
Preparing synchronization components in Azure
You need Azure subscription linked to an Azure AD tenant that is a destination for synced accounts. Create all resources in a dedicated resource group.
User managed identities
Create user assigned managed identity in Azure Portal or any other method. Then assign application User.ReadWrite.All and Group.Read.AllGraph API permission. Granting permissions for managed identity (which is Azure managed service principal) is not available in the Portal but could be done using PowerShell script (reference):
Key Vault
Create Key Vault using favorite method. Additional configuration:
- Change permission model to Azure role-based access control (preview)
- Assign Key Vault Secrets User (preview) role for user-managed identity we created
Azure Storage Account Table
Create Azure Storage in resource group using method of your choice. Easiest way is again in Azure Portal.
To edit rows and manage tables in Azure storage accounts, the free Azure Storage Explorer could be used. After launching the tool, sign-in to Azure AD account with appropriate permissions.
Five tables need to be created manually:
ApplicationConfiguration
Contains general configuration for our sync engine. Data is filled ny administrator manually. Main purpose of this table is to avoid using variable constants in Logic Apps. It contains only one entry with two properties
Property | Type | Description |
---|---|---|
DestinationTenantPrimaryDomain | String | Primary domain of a destination tenant (to form UPN during attribute sync) |
KeyVaultURL | String | URL of Key Vault storing service principal id and secrets |
PartitionKey | String | Various value |
RowKey | String | Various value |
DeltaLinkGroups
Storage of delta links for sync groups in remote tenants. Workflows fill data inside.
When synchronization is running, delta links for sync groups are stored in the DeltaLinkGroups table. The table has the following properties
Property | Type | Description |
---|---|---|
PartitionKey | String | Source tenant Id |
RowKey | String | Sync group Id is source tenant |
DeltaLink | String | Delta link to Graph API query |
To start initial full sync for a particular tenant, remove corresponding entity from DeltaLinkGroups. This will initiate invitation workflow to go through every member in sync group and rewrite delta links in DeltaLinkUsers.
DeltaLinkUsers
Storage of delta links of synced user objects in destination tenant. Workflows fill data inside. DeltaLinkUsers has the following properties:
Property | Type | Description |
---|---|---|
PartitionKey | String | Source tenant Id |
RowKey | String | User object id in source tenant |
DeltaLink | String | Delta link to Graph API query |
String | Mail attribute of user object in source tenant | |
UserId | String | User object id is destination tenant |
To manually initiate full attribute sync for a particular user, DeltaLink property can be removed from the table. Important: not the whole entity but only the property should be removed. Removing complete row for a user would stop attribute sync for user until invitation workflow restores the record.
Logs
Stores logs of automation. Workflows fill data inside.
Property | Type | Description |
---|---|---|
PartitionKey | String | Source tenant Id |
RowKey | String | Random GUID |
Action | String | Action performed by invitation or attribute sync workflows |
DateTime | String | Date and time in UTC of an action. Timestamp property is built-in property and shows when data was written to table |
ErrorMessage | String | Error or informational message |
ObjectId | String | User Object Id in destination tenant |
TenantName | String | Name of the tenant as appears in RemoteTenantConfiguration table |
UserMail | String | Email used to invite user |
RemoteTenantConfiguration
List of all tenants the engine synchronizes user objects from. Most important sync engine configuration is stored here. Data is filled by administrator manually before starting the sync.
Property | Type | Description |
---|---|---|
PartitionKey | String | Various value, for example B2B |
RowKey | String | Remote tenant name (to identify easier) |
TenantId | String | Tenant Id of source (remote) tenant |
ApplicationId | String | Application Id of service principal in source tenant to perform Graph API calls |
GroupId | String | Group Id of synchronization group |
EmailDomain | String | List of all allowed domains used for primary SMTP addresses of users in source tenant. If there is more than one, they should be separated using ‘,’ or ‘;’ |
InvitedUserType | String | Must be member or guest. This is InvitedUserType property of Invitation Graph API type |
SendInvitationMessage | Boolean | Indicates whether an email should be sent to an invited user or not. This is SendInvitationMessage property of Invitation Graph API type |
InviteRedirectUrl | String | The URL the user should be redirected to once the invitation is redeemed. This is InviteRedirectUrl property of Invitation Graph API type |
CustomizedMessageBody | String | Customized message body. This is customizedMessageBody property of invitedUserMessageInfo Graph API object |
DisplayName | String | Display name suffix of invited user (obsolete) |
Enabled | Boolean | If Enabled is false, no sync will be performed |
AttributeSyncEnabled | Boolean | If Enabled is false, no attribute sync will be performed (only invitation) |
SyncedAttributes | String | Comma separated list of synced attributes |
AttributeSyncExclusion | String | Comma separated list of object Ids excluded from attribute sync |
Properties of Invitation Graph API type are described in this Microsoft article.
Here is an example of most common attributes that could be specified in SyncedAttributes
property:
As changing attributes of user with Azure AD roles is a sensitive operation, user.readwrite.all permission will not be sufficient for managed identity. Therefore, primary purpose of AttributeSyncExclusion is to exclude external users who have privileged roles in destination tenant and to avoid failing attempts to sync attributes.
Configuring remote Azure AD tenants for sync
In source tenants only two objects should be created in Azure AD.
-
Sync group. Members of the group will be invited to destination tenant. This is a security group in Azure AD. Object id of this group should be stored in RemoteTenantConfiguration table.
-
Service principal. Easiest way is to create it in Portal as described here. Add client secret as credentials, copy it along with service principal application id and tenant id.
Grant these application (not delegated) permissions for the application as described here and grant admin consent:
Microsoft Graph Group.Read.All Microsoft Graph User.Read.All
Service principals’ secrets are placed into Key Vault in destination tenant with a name equal to application Id (not a tenant id!) of corresponding service principal object.
Final step: preparing Logic Apps.
Create API connection to storage account created before. It’s easy to perform by choosing any actions from Azure Table Storage connector and then clicking on Add new connection.
Create three empty Logic Apps. Configure each of them to use previously created user managed identity.
For additional security both workers apps can be configured for being triggered only from other LogicApps
Open code view of both workers and paste code from GitHub. At the very bottom change parameters for $connections
to contain proper path to Azure Table connector. For main workflow change also workflow IDs (resource Id that is full path to Logic Apps workers).
Sync Engine Monitoring
Logs table gives comprehensive information about how invitation and sync attributes workflows worked.
Azure AD audit log is way to monitor all cha at tenant level that includes also activities from sync engine. Note, managed identity is shown as subject that performed changes