Avoid the complexity when utilizing Entra ID multi-tenants and School or Work/Microsoft Accounts
Published Feb 14 2024 10:57 AM 3,672 Views
Microsoft

Ideally, it would be convenient to manage the Prod env, Test env, and Dev env within a single Entra ID tenant. However, from the perspective of governance and compliance, it is common to separate the Entra ID tenant for the Prod env from the other envs. In such cases, various complexities arise, such as guest invitations for School or Work Account, use of Microsoft Account, and individuals using multiple accounts.
I recently had challenge when I attempted to control access to Cosmos DB using RBAC, as described in Use system-assigned managed identities to access Azure Cosmos DB data article. While this topic itself is simple, it becomes more intricate when dealing with the subject.

If you don't fall into any of the categories listed below, you're fine. However, many of you may encounter issues at some point. The initial item alone might not be issue, but when the second, third, and fourth items come into play, the complexity of Entra ID shows up.

  1. Use your local machine for the development.
  2. Leverage Entra ID multi-tenants and guest invitation.
  3. Use Microsoft account instead of School or Work account.
  4. Use both of Microsoft account and School or Work account.

I have tried a couple of scenarios, so I will share knowledge which work well or not work well in this post.

#1: Single Entra ID tenant, a subscription exists within the same EntraID tenant, and using School or Work account in the same EntraID tenant - This works well.

Let's try the official article at first. The concept is illustrated in the diagram below:

20240206055501 There is no built-in role for Cosmos DB data access, so we need to create the role as custom role. In this example, we create the custom role, which can read, write, and delete the data. The JSON file is as follows:

 

{
    "RoleName": "CosmosDBDataAccessRole",
    "Type": "CustomRole",
    "AssignableScopes": ["/"],
    "Permissions": [{
        "DataActions": [
            "Microsoft.DocumentDB/databaseAccounts/readMetadata",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*",
            "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*"
        ]
    }]
}

 

Then, you create this custom role on your environment. You can get the resource ID of the custom role here as format, "XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX" (actually a randomly generated UUID). This will be used later.

 

$rgName = "your-resource-group-name"
$cosmosdbName = "your-cosmosdb-name"
az cosmosdb sql role definition create -a $cosmosdbName -g $rgName -b role-definition-rw.json  
{
  "assignableScopes": [
    "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name"
  ],
  "id": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleDefinitions/XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX",
  "name": "XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX",
  "permissions": [
    {
      "dataActions": [
        "Microsoft.DocumentDB/databaseAccounts/readMetadata",
        "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*",
        "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*"
      ],
      "notDataActions": []
    }
  ],
  "resourceGroup": "your-resource-group-name",
  "roleName": "CosmosDBDataAccessRole",
  "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions",
  "typePropertiesType": "CustomRole"
}

 

Next, you need to obtain the ID of your School or Work account using the following "az ad user show" command:

 

az ad user show --id "myuser@normalian.xxx"
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "aduser-normalian",
  "givenName": "aduser",
  "id": "YYYYYYY-YYY-YYY-YYYY-YYYYYYYYY",
  "jobTitle": "Principal Administrator",
  "mail": "myuser@normalian.xxx",
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": "normalian",
  "userPrincipalName": "myuser@normalian.xxx"
}

 

Finally, assign the custom role to the user.

 

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "YYYYYYY-YYY-YYY-YYYY-YYYYYYYYY" -d "XXXXXXX-XXX-XXX-XXXX-XXXXXXXXX" -s "/"
{
  "id": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleAssignments/787a36f9-
a7f3-40c8-a860-99d4c6ae5fd9",
  "name": "787a36f9-a7f3-40c8-a860-99d4c6ae5fd9",
  "principalId": "b0bde25a-d588-410c-a16d-30fc001661c4",
  "resourceGroup": "your-resource-group-name",
  "roleDefinitionId": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleDefinit
ions/26eeca83-1fd8-4f40-8a5e-b66dac7d3e08",
  "scope": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name",
  "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments"
}

 

In your source code, you can leverage the authentication info. Refer to the Programmatically access the Azure Cosmos DB keys article if you need the detail more. This method allows you to access Cosmos DB without secret strings.

 

using Azure.Identity;
using Microsoft.Azure.Cosmos;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
    return new CosmosClient(
        accountEndpoint: builder.Configuration["AZURE_COSMOS_DB_NOSQL_ENDPOINT"]!,
        tokenCredential: new DefaultAzureCredential()
    );
});

var app = builder.Build();

 

I believe this scenario should be simple.

#2: You have EntraID multi-tenants, and You use a subscription on different EntraID tenant from School or Work Account's one - This works well

Let's try another scenario. I believe this one is very popular in large companies. This scenario use School or Work Account on production env Entra ID tenant and use the account as a guest use on development env Entra ID tenant. This scenario also assume that your subscription is on development env EntraID tenant. This scenario diagram is as follows:

20240206055647

Here are two ket points in this scenario:

  • How to assign custom roles to the guest user
  • How to use the development env EntraID tenant with DefaultAzureCredential

First, I try to get resource id for the guest user as follows, but this does not work well.

 

$ az ad user show --id "myuser01@normalian.xxx"
az : ERROR: Resource 'myuser01@normalian.xxx' does not exist or one of its queried reference-property objects are not present.
At line:1 char:1
+ az ad user show --id "myuser01@normalian.xxx"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: Resource...re not present.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

 

You can figure out why this does not work well by checking your development env Entra ID tenant. The guest use contains "#EXT#@" and appear as follows:

20240203103500

You can get the resource id here. You can also get the ID with the command as follows.

 

az ad user show --id "daichi_mycompany.com#EXT#@normalianxxxxx.onmicrosoft.com"
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "daichi",
  "givenName": null,
  "id": "4bf33ec0-dc63-4468-cc7d-edd4c9820fee",
  "jobTitle": "CLOUD SOLUTION ARCHITECT",
  "mail": "daichi@mycompany.com",
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": null,
  "userPrincipalName": "daichi_mycompany.com#EXT#@normalianxxxxx.onmicrosoft.com"
}

 

You can assign the custom role properly with the ID using "az cosmosdb sql role assignment create" command.

Next, you need to configure to use development env EntraID tenant in your code. The School or Work account uses production env EntraID tenant without any setting, then you will find "organizational account belongs to the production EntraID, but Azure subscription is under the development EntraID" error as follows. (This example is ASP.NET Core):

20240203112655

To avoid this, you can address this with the code as follows:

 

using Azure.Identity;
using Microsoft.Azure.Cosmos;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
//    var connectionString = builder.Configuration.GetConnectionString("CosmosDB");
//    return new CosmosClient(connectionString);
    var option = new DefaultAzureCredentialOptions()
    {
        TenantId = "your-entraid-tenant-id",
    };

    return new CosmosClient(
        accountEndpoint: builder.Configuration["AZURE_COSMOS_DB_NOSQL_ENDPOINT"]!,
        tokenCredential: new DefaultAzureCredential(option)
    );
});

var app = builder.Build();

 

By setting up the tenant ID here, you can access proper Entra ID tenant.

#3: Use Microsoft account invited as a guest user on EntraID tenant - this works well

This case is same concept with second use case. This is a diagram for the scenario as follows:

20240206060705

Run "az ad user show" command with the account name adding #EXT#@ and your EntraID tenant. It's fine to check on your Entra ID tenant directly. Then, run "az cosmosdb sql role assignment create" command to assign the custom role to the use.

 

az ad user show --id "warito_test_hotmail.com#EXT#@normalianxxxxx.onmicrosoft.com"
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "Daichi",
  "givenName": "Daichi",
  "id": "94bfd636-b2e9-4d44-b895-8d51277e7abe",
  "jobTitle": "Principal Normal",
  "mail": null,
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": "Isamin",
  "userPrincipalName": "warito_test_hotmail.com#EXT#@normalianxxxxx.onmicrosoft.com"
}

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "your-user-object-resourceid" -d "your-customrole-resourceid" -s "/"
{
  "id": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleAssignments/7adc585c-
74d6-4979-a3ed-3d968de2d27e",
  "name": "7adc585c-74d6-4979-a3ed-3d968de2d27e",
  "principalId": "b0bde25a-d588-410c-a16d-30fc001961c4",
  "resourceGroup": "your-resource-group-name",
  "roleDefinitionId": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name/sqlRoleDefinit
ions/7adc585c-74d6-4979-a3ed-3d968de2d27e",
  "scope": "/subscriptions/your-subscription-id/resourceGroups/your-resource-group-name/providers/Microsoft.DocumentDB/databaseAccounts/your-cosmosdb-name",
  "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments"
}

 

#4 Create Service Principal on development env Entra ID tenant - This does not work.

I guess you might come up an idea "Why not just create service principal in the development env Entra ID tenant?" Here is a diagram for this scenario as follows. This approach does not work.

Note that the Client ID and Object ID of the Service Principal are different. When you run commands to assign the custom role, it appears as follows:

 

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "your-serviceprincipal-clientied" -d "your-customrole-resourceid" -s "/"
az : ERROR: (BadRequest) The provided principal ID ["your-serviceprincipal-clientied"] was not found in the AAD tenant(s) [b4301d50-52bf-43f0-bfaa-915234380b1a] which are associated 
with the customer's subscription.
At line:1 char:1
+ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: (BadRequ...s subscription.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
ActivityId: b1b3a860-c244-11ee-9296-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0
Code: BadRequest
Message: The provided principal ID ["your-serviceprincipal-clientied"] was not found in the AAD tenant(s) [b4301d50-52bf-43f0-bfaa-915234380b1a] which are associated with the 
customer's subscription.
ActivityId: b1b3a860-c244-11ee-9296-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0

$ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p "your-serviceprincipal-objectid" -d "your-customrole-resourceid" -s "/"
az : ERROR: (BadRequest) The provided principal ID ["your-serviceprincipal-objectid"] was found to be of an unsupported type : [Application]
At line:1 char:1
+ az cosmosdb sql role assignment create -a $cosmosdbName -g $rgName -p ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: (BadRequ...: [Application]:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
ActivityId: f24dccfd-c244-11ee-9712-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0
Code: BadRequest
Message: The provided principal ID ["your-serviceprincipal-objectid"] was found to be of an unsupported type : [Application]
ActivityId: f24dccfd-c244-11ee-9712-085bd676b6c2, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, 
Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0, Microsoft.Azure.Documents.Common/2.14.0

 

As evident from the above, you get an error stating, "There's no such ID in the first place". This is expected since the object ID is not specified. In second, you get an error stating, "Assigning to the Application is unsupported!" when you use the Object ID. Take note of this.

#5: Use multi-accounts on EntraID multi-tenants - This case works

This scenario is popular when you manage multiple customers simultaneously. Let's assume the following example for illustration.

  • Account for Project ①: myuser@normalian.xxx - School or Work account
  • Account for Project ②: personalxxxx@outlook.com - Microsoft account

We have to look back authentication priority of DefaultAzureCredential. Refer to DefaultAzureCredential class for more details.

In this case, we switch the accounts using Azure Cli for AzureCliCredential. First, run command as follows and acquire authentication info for your development account.

 

az login

 

Next, refer to the example source code as follows. You can configure the priority.

 

using Azure.Identity;
using Microsoft.Azure.Cosmos;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
//    var connectionString = builder.Configuration.GetConnectionString("CosmosDB");
//    return new CosmosClient(connectionString);
    var option = new DefaultAzureCredentialOptions()
    {
       ExcludeEnvironmentCredential = true,
       ExcludeWorkloadIdentityCredential = true,
       ExcludeManagedIdentityCredential = true,
       ExcludeSharedTokenCacheCredential = true,
       ExcludeVisualStudioCredential = true,
       ExcludeVisualStudioCodeCredential = true,
       TenantId = "your-entraid-tenant-id",
    };

    return new CosmosClient(
        accountEndpoint: builder.Configuration["AZURE_COSMOS_DB_NOSQL_ENDPOINT"]!,
        tokenCredential: new DefaultAzureCredential(option)
    );
});

 

Reference 

3 Comments
Co-Authors
Version history
Last update:
‎Feb 14 2024 09:30 PM
Updated by: