I'm fairly confident I set everything up right for a multi-tenant app.
In Tenant A: I created an Azure function and enabled a system managed identity. I granted the Managed Identity permissions to the Graph API. I confirmed my API can obtain an access token for the Managed Identity and access the Graph API with it. I added Azure AD authentication to the app and created an App Registration for the app. I configured the necessary Graph API permissions in the API Permissions settings of the app registration. I also enabled the various options to enable multi-tenant access.
In Tenant B: I accessed the special URL to start the admin consent process for the app in Tenant A. I confirmed that I was prompted to consent to the permissions that I specified in the app registration in Tenant A. I can see that a couple new enterprise application entries were created in Tenant B for the app in Tenant A.
So far so good. However, no matter what I do, I cannot obtain a token to access the graph API in the context of Tenant B. It ALWAYS gives me a token for the managed identity in Tenant A, and accesses the info in Tenant A.
The code the retrieves an access token using the managed identity is here:
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { AdditionallyAllowedTenants = { "*" }, TenantId = "<Tenant B ID>" });
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }, null, null, "<Tenant B ID"));
var accessToken = token.Token;
Now, I have tried all sorts of different combinations here. But, from everything I read, I am supposed to be able to specify the Tenant ID of another tenant in these methods and obtain an access token for that specific tenant. However, even when I specify Tenant B's ID, I always get a token back for the managed identity in Tenant A. If I use the token, of course, I end up accessing all of Tenant A's information.
Ironically, if I remove the DefaultAzureCredentialOptions
and include the tenantID in the GetToken
request I get an error telling me that I'm trying to obtain a token for a different tenant (than my own) and that I need to add the AdditionallyAllowedTenants
option. It clears the error up when I add that, but then it doesn't obtain a token for the other tenant.
Perhaps I am still approaching this wrong. I want to host a multi-tenant Azure Function that runs in the context of the Tenant where the request originated from to access information within that tenant using a managed identity that exists within that tenant. I can obtain the Tenant ID context from the claims sent to the Azure function during authentication, but no matter how I try to specify that Tenant ID in my code, it will not get a token for that Tenant.
UPDATE and SOLUTION: Thanks to Philippe's great write up, I did finally get this. I had some confusion around the mechanisms at play here and want to add this note for clarity on how I solved it.
If you've followed most of the documents you have an Azure Function app in your "host" tenant A, with an associated app registration in tenant A. The app registration has certain API permissions configured, and you have consented to this app and permissions in Tenant B.
To access the resources in Tenant B, you need to create a secret key or certificate on the app registration in Tenant A. You then need to authenticate using the Client ID, and secret key/certificate of the app registration in Tenant A, but you will request a token for Tenant B. Here it is in code using a certificate for authentication:
var appToken = new ClientCertificateCredential(tenantID, appID, appCert, new ClientCertificateCredentialOptions { AdditionallyAllowedTenants = { "*" } });
var graphServiceClient = new GraphServiceClient(appToken);
Here, we assign the following to the variables:
- tenantID = Tenant B's ID
- appID = Tenant A's App registration client ID
- appCert = Tenant A's App registration cert
We have to include the AdditionallyAllowedTenants config parameter, and the *
authorizes a token from any tenant. We then take that credential and use it to build a new GraphServiceClient
that is connected to Tenant B.