This article is in support of CloudRadial's open-source project for Azure Functions. Please see https://github.com/cloudradial/CloudRadialCsaAutomations for more information.
Azure Functions provides a way to run PowerShell scripts on demand and with the Microsoft.Graph module offers an easy way to make changes in a 365 tenant. This allows you to move scripts you run locally to the cloud. For Partners managing multiple customer tenants through a CSP relationship, a single Azure Function App can authenticate against any customer tenant on demand. There is no need to create separate Function Apps or App Registrations per customer.
Local execution typically starts with the command:
Connect-MgGraph
This command opens a login window so you can enter your user credentials. This same command is used in an Azure Function, but you need to authenticate through an application that will act as a user to perform the requested actions. The application must have the necessary rights to act. To create this application, please see this article: Creating a Microsoft Entra ID App Registration for Azure Function Authentication
- Creating a Function App
- Storing Your Credentials as Environment Variables
- Preparing for Multi-Tenant Access
- Authenticating to Microsoft Graph
- Passing the Tenant ID From a CloudRadial Automation
- End-to-End Flow
- Common Errors
- Security Recommendations
Creating a Function App
If you have not already created an Azure Function App for PowerShell scripts, from the Azure Portal:
- Search for Function App using the search bar at the top of the page
- Choose the Create option and fill in the details.
- Be sure to choose PowerShell Core for the runtime stack, and for most cases, you can use the Consumption (Serverless) option
Storing Your Credentials as Environment Variables
Now, you have recorded the Application ID, the Tenant ID, and the Client Secret value from your App Registration. You will use these values to authenticate your PowerShell scripts.
While you can place these values directly into your script, a better method is to save them in the configuration section of your Azure Function. Return to your Azure Function application and choose Configuration under the Settings area.
Under Application Settings, choose the New application setting and create the following:
-
Ms365_AuthAppId— your Application (client) ID -
Ms365_AuthSecretId— your Client Secret value -
Ms365_TenantId— your default Tenant ID. For Partners operating across multiple customer tenants, this serves as a fallback when no Tenant ID is provided in the request
You will use the names of these settings in your script.
Note: To use Microsoft.Graph commands, you must first install the Microsoft.Graph module in the Azure Function. Jump to this article to learn how: Installing PowerShell Modules in Azure Functions
Preparing for Multi-Tenant Access
Multi-Tenant access relies on Microsoft's CSP and GDAP infrastructure. The steps below explain what needs to be in place and link to the relevant Microsoft documentation. For questions about GDAP relationships, role assignments, or the consent process, refer to the linked Microsoft documentation or contact Microsoft Partner Support.
If your Automations only need to target a single tenant, you can skip this section and proceed to Authenticating to Microsoft Graph. If you manage multiple customer tenants and want your Functions to work across all of them, the following three things must be in place before your Function can authenticate to a customer tenant.
Your App Registration Must Be Multi-Tenant
The App Registration article instructs you to choose "Accounts in any organization directory" during creation. If you did this, you are already set.
If you are unsure, check the Authentication settings on your App Registration in the Azure Portal. The Supported account types should be set to "Accounts in any organizational directory (Any Microsoft Entra ID tenant – Multitenant)." If it is set to single-tenant, change it and save. Your Application ID and Client Secret remain the same.
Your App Must Be in the AdminAgents Group
Every CSP Partner tenant has a security group called AdminAgents that Microsoft creates automatically. Your app's service principal must be a member of this group for Microsoft's identity platform to recognize it as a Partner-managed application.
This is a one-time configuration. Microsoft documents the process here:
Call Microsoft Graph from a Cloud Solution Provider application (Microsoft Learn)
Each Customer Tenant Must Have Application Consent
Under the legacy DAP model, adding your app to AdminAgents automatically preconsented it in every customer tenant. With GDAP, each customer tenant must explicitly consent to your application. This is the most common cause of the Authorization_IdentityNotFound error.
Manual consent (one customer at a time): The App Registration article provides a consent URL:
https://login.microsoftonline.com/common/adminconsent?client_id={Your Application (client) Id}Open this in an incognito browser, sign in as an admin from the customer's organization, and accept the permissions. You may receive an error about not having a callback URL, but the application should now show up in your client's list of Enterprise Applications.
Automated consent (bulk, via Partner Center): Microsoft provides a PowerShell cmdlet (New-PartnerCustomerApplicationConsent) and a REST API to grant consent across customer tenants programmatically. This requires an active GDAP relationship that includes the Cloud Application Administrator role.
- Microsoft documents the full requirements and process here:
- The following community resource also walks through the GDAP consent process with practical examples:
Authenticating to Microsoft Graph
Single-Tenant Authentication
If your Function only needs to target one tenant, use the following code block at the start of your Function:
$tenantId = $env:Ms365_TenantId $appId = $env:Ms365_AuthAppId $appSecret = $env:Ms365_AuthSecretId $securePassword = ConvertTo-SecureString -String $appSecret -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($appId, $securePassword) Connect-MgGraph -ClientSecretCredential $credential -TenantId $tenantId
Multi-Tenant Authentication
If you completed the steps in Preparing for Multi-Tenant Access, the only change is how the Tenant ID is determined. Instead of always reading the static environment variable, the Function checks the incoming request body first and falls back to the default if no Tenant ID is provided:
$tenantId = if ($Request.Body.tenantId) {
$Request.Body.tenantId
} else {
$env:Ms365_TenantId
}
$appId = $env:Ms365_AuthAppId
$appSecret = $env:Ms365_AuthSecretId
$securePassword = ConvertTo-SecureString -String $appSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($appId, $securePassword)
Connect-MgGraph -ClientSecretCredential $credential -TenantId $tenantIdThe same App Registration credentials work against any customer tenant where consent has been granted. The only thing that changes per request is $tenantId.
After either authentication block, you can reference any Microsoft.Graph PowerShell command.
- More information about the Microsoft.Graph PowerShell module can be found at https://learn.microsoft.com/en-us/powershell/microsoftgraph/overview
Passing the Tenant ID From a CloudRadial Automation
When you build an Automation under Partner > Automations and add webhook steps that call your Azure Function, you pass data from the Service Catalog form using tokens. The Triggering Azure Functions from Automations article explains how tokens and webhook bodies work.
To target a specific customer tenant, include the customer's Microsoft 365 Tenant ID in the webhook body alongside your other form field tokens:
{
"tenantId": "@CompanyMs365TenantId",
"userPrincipalName": "@UserPrincipalName",
"displayName": "@DisplayName",
"groupId": "@GroupId"
}Check the token picker in the Automation editor for available company-level tokens. If you are unsure which token contains the customer's Microsoft 365 Tenant ID, you can find it under Partner > Clients > [Client] > Edit > Integrations where the Tenant ID is displayed for each company connected to Microsoft 365.
End-to-End Flow
With everything in place, a Multi-Tenant Automation works like this:
- An end user at Customer ABC submits a Service Catalog form (e.g., "Add User to Group")
- CloudRadial triggers the Automation, populating the webhook body with Customer ABC's Tenant ID and the form field values
- Your Azure Function reads
tenantIdfrom the request body, authenticates to Customer ABC's tenant, and executes the Graph command - The result flows back through the Automation's subsequent steps — ticket notes, status updates, confirmation emails — as described in the Triggering Azure Functions from Automations article
The same Function App, same App Registration, and same code handles this for every customer.
Common Errors
| Error | What It Likely Means |
|---|---|
Authorization_IdentityNotFound |
Application Consent has not been granted in the target customer tenant. Refer to the Application Consent section in Preparing for Multi-Tenant Access |
AADSTS700016: Application not found in the directory |
The App Registration is set to single-tenant instead of Multi-Tenant |
Insufficient privileges to complete the operation |
The GDAP relationship with this customer does not include the Entra ID roles needed for the Graph command your Function is running (e.g., User Administrator for user creation) |
Access Denied on a brand-new customer |
Partner relationships can take up to 3 minutes to propagate. Wait and retry with a fresh token request |
Security Recommendations
- Only request the Microsoft Graph API permissions your Functions actually use. If your Automations only create users and manage groups, you do not need broad permissions like
Directory.ReadWrite.All - Request the minimum Entra ID roles required for your Automations in your GDAP relationships. Microsoft provides a breakdown by task in their GDAP least-privileged roles documentation
- Set a reminder to rotate your App Registration Client Secret before it expires. When you rotate it, update the
Ms365_AuthSecretIdenvironment variable in your Azure Function configuration - For production environments, consider certificate authentication.
Connect-MgGraphsupports the-CertificateThumbprintparameter as a more secure alternative to-ClientSecretCredential
Related Articles
- Creating a Microsoft Entra ID App Registration for Azure Function Authentication
- Triggering Azure Functions from Automations
- GDAP: Connecting Tenants to Microsoft 365 Now Requires Consent
- CloudRadial CSA Automations Repository (GitHub)
- Call Microsoft Graph from a CSP Application (Microsoft Learn)
- Migrate CPV/CSP Applications to GDAP (Microsoft Learn)
- GDAP Least-Privileged Roles by Task (Microsoft Learn)
If you are still having trouble, we're here to help!
Submit a ticket here for assistance, and don't forget to check our status page to ensure there are no outages in your area.
Comments
0 comments
Article is closed for comments.