Manage Cross Tenant Resources using Entra ID App with Federated Credential and Managed Identity

  • Think about a use case where you have two tenants – Tenant 1 (we call it Azure tenant with Azure resources and M365 resources) and Tenant 2 (we call it M365 tenant with M365 resources but zero Azure resources).
  • You need to manage M365 resources in M365 tenant where there is NO compute (VM or serverless).
  • Tenant 1 complies with all security controls – access provisioning, credential management, authentication/authorization, network security, data security, logging/monitoring and operational security and vulnerability management.
  • Tenant 2 is used as testing non-production M365 resources and their configurations. Tenant 2 does not have same security controls like you do in Tenant 1.
  • You want to manage resources in Tenant 2, but you would like to use compute in Tenant 1 to manage them.
  • You want to use Managed Identity in Tenant 1 so that you don’t have to deal with credentials.
  • How do you solve the business problem? You will need to comply with company’s policies and standards, and you want to maintain the security controls in both tenant.

Welcome to Microsoft Entra ID application with Federated Credential and Managed Identity! In this blog post, I would like to demonstrate how you can use Azure Automation with System Managed Identity in Tenant 1 to manage M365 resources in Tenant 2. Let’s get to the business!

Cross Tenant Application with Federated Credential

(1) Create Azure Automation with System Managed Identity in Tenant 1 (Azure tenant). Let’s call the automation as AzAutomationDemo.

(2) Create an App Registration in Tenant 1 and make it multi-tenant application. Assign graph permission as required, but in our demo we will assign Sites.FullControl.All and User.Read. Name the app as ManagedIdentityDemo-AzureTenant. Note the Application ID or clientid.

(3) Login to Tenant 2 (M365 tenant) with Global Admin role and grant admin consent to the multi-tenant app created in Tenant 1. Sample admin consent format: https://login.microsoftonline.com/{m365-tenantid}/adminconsent?client_id={azure-tenant-clientId}. Once admin consent is granted, a Service Principal is created in Tenant 2 and you can find it under Enterprise Applications under Entra ID.

(4) Come back to Azure Tenant and create a PowerShell (v7.2) Runbook. We are calling the runbook as FederatedIdentityCredentialDemo.

(5) Get Access Token from Entra ID of Tenant 1 from FederatedIdentityCredentialDemo runbook.

#Get Access Token by using Managed Identity from Azure Tenant
$url = $env:IDENTITY_ENDPOINT  
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
$headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER) 
$headers.Add("Metadata", "True") 
$body = @{resource='api://AzureADTokenExchange' } 
$accessToken = Invoke-RestMethod $url -Method 'POST' -Headers $headers -ContentType 'application/x-www-form-urlencoded' -Body $body 
Write-Output "Access token from Azure Tenant:  $($accessToken.access_token)"

(6) We are going to use the access token from Tenant 1 and exchange it to get access token from Tenant 2 that is required to call M365 resources in Tenant 2. In real world, you don’t print/log access_token, we are doing here for demo!

#Exchange Managed Identity access token for app access token from M365 tenant
$clientId = "clientid-of-azure-tenant"
$m365tenantid ="your-m365tenantid"
$granttype = "client_credentials"
$scope ="https://graph.microsoft.com/.default"
$assertiontype = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
$contenttype = "application/x-www-form-urlencoded"

$tokenEndpoint = "https://login.microsoftonline.com/$($m365tenantid)/oauth2/v2.0/token"
$hdrs = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
$m365requestbody = @{
    grant_type = $granttype
    client_assertion_type = $assertiontype
    client_id = $clientId
    scope = $scope
    client_assertion = $accessToken.access_token
}

Write-Output "m365requestbody: $($m365requestbody | ConvertTo-Json)"

$m365response = Invoke-RestMethod $tokenEndpoint -Method 'POST' -Headers $hdrs -ContentType $contenttype -Body $m365requestbody
$m365accessToken = $m365response.access_token

Write-Output "Access token from M365 Tenant:  $($m365accessToken)"

(7) Now, we are ready to call M365 resource via graph api and pass the access token received from Tenant 2. In our demo, we will get list of SharePoint sites under Tenant 2.

#Get list of sharepoint sites
$hdrs2 = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$hdrs2.Add("Content-type", "application/json") 
$hdrs2.Add("Authorization", "Bearer $($m365accessToken)") 

$response2 = Invoke-RestMethod "https://graph.microsoft.com/v1.0/sites?$select=siteCollection,webUrl&$filter=siteCollection/root%20ne%20null" -Method 'GET' -Headers $hdrs2
Write-Output "List of sites: $($response2 | ConvertTo-Json)"

Just to summarize our work, let us depict this diagram to show the token exchange flow-

Cross Tenant Resource Management with Managed Identity and Entra ID app with Federated credential

To put all the PowerShell codes together –

#https://learn.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation

#Get Access Token by using Managed Identity from Azure Tenant
$url = $env:IDENTITY_ENDPOINT  
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
$headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER) 
$headers.Add("Metadata", "True") 
$body = @{resource='api://AzureADTokenExchange' } 
$accessToken = Invoke-RestMethod $url -Method 'POST' -Headers $headers -ContentType 'application/x-www-form-urlencoded' -Body $body 
Write-Output "Access token from Azure Tenant:  $($accessToken.access_token)"

#Exchange Managed Identity access token for app access token from M365 tenant
$clientId = "clientid-of-azure-tenant"
$m365tenantid ="your-m365tenantid"
$granttype = "client_credentials"
$scope ="https://graph.microsoft.com/.default"
$assertiontype = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
$contenttype = "application/x-www-form-urlencoded"

$tokenEndpoint = "https://login.microsoftonline.com/$($m365tenantid)/oauth2/v2.0/token"
$hdrs = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
$m365requestbody = @{
    grant_type = $granttype
    client_assertion_type = $assertiontype
    client_id = $clientId
    scope = $scope
    client_assertion = $accessToken.access_token
}

Write-Output "m365requestbody: $($m365requestbody | ConvertTo-Json)"

$m365response = Invoke-RestMethod $tokenEndpoint -Method 'POST' -Headers $hdrs -ContentType $contenttype -Body $m365requestbody
$m365accessToken = $m365response.access_token

Write-Output "Access token from M365 Tenant:  $($m365accessToken)"


#Get list of sharepoint sites
$hdrs2 = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$hdrs2.Add("Content-type", "application/json") 
$hdrs2.Add("Authorization", "Bearer $($m365accessToken)") 

$response2 = Invoke-RestMethod "https://graph.microsoft.com/v1.0/sites?$select=siteCollection,webUrl&$filter=siteCollection/root%20ne%20null" -Method 'GET' -Headers $hdrs2
Write-Output "List of sites: $($response2 | ConvertTo-Json)"

Comments are disabled at the blogs to avoid spams. If you have any feedbacks or comments, you are welcome to contact me overĀ LinkedIn.

Acknowledgement: https://github.com/arsenvlad/entra-cross-tenant-app-fic-managed-identity

Leave a Reply