Search code examples
c#azureazure-ad-msalazure-ad-b2c

Creating a B2C Tenant via the REST API returns 401/Unauthorized


What I want to do: create and configure a B2C tenant via the REST API.

What I did:

I created an app registration named CDTester and a secret for it in my default directory, then added it to the target subscription with the role Contributor.

I got a config object cfg loaded from user-secrets with the application id and secret of CDTester, with the tenant id of my default directory and with the id of the target subscription.

In code I authenticate with ConfidentialClientApplicationBuilder.

Then I use the token I get to do a few things:

  1. I check for the existence of a specific resourcegroup
  2. If it doesn't exist, I create it
  3. I check whether the desired name for the B2C tenant is available
  4. I (try to) create the tenant.

Steps 1-3 work fine. But step 4 gives me a 401/Unauthorized. The body of the response is formatted as content-type HTML (instead of JSON) and contains only

You do not have permission to view this directory or page.

Because steps 1-3 work I know that I am authenticating correctly. I can even access the B2C specific API (which I use in step 3).

Next I went into the Portal and made CDTester an owner of the subscription, so there ought to be no action it is not allowed to do. Alas, to no avail, I got the same result.

For reference, here are relevant portions of my code:

// getting the access token
var app = ConfidentialClientApplicationBuilder
    .Create(authConfig.ApplicationId)
    .WithTenantId(authConfig.LoginTenantId)
    .WithClientSecret(authConfig.ApplicationSecret)
    .Build();
var authResult = await app.AcquireTokenForClient(new[] {"https://management.azure.com/.default"})
                          .ExecuteAsync();

// I use FlurlHttp and set the token
FlurlHttp.Configure(s => s.BeforeCall = c => c.Request.Headers.Add("Authorization", $"Bearer {authResult.AccessToken}"));

// this call works just fine
var nameAvailabilityResponse =  await $"https://management.azure.com/subscriptions/{authConfig.SubscriptionId}/providers/Microsoft.AzureActiveDirectory/checkNameAvailability?api-version=2019-01-01-preview"
                                        .PostJsonAsync(new
                                        {
                                            name = creation.FullDirectoryName,
                                            countryCode = creation.CountryCode
                                        })
                                        .ReceiveJson();
if (!nameAvailabilityResponse.nameAvailable)
{
    await Console.Error.WriteLineAsync(nameAvailabilityResponse.message);
    return ExitCodes.DirectoryNameIsNotAvailable;
}

var displayName = creation.EnvironmentName == "production"
    ? "DEON"
    : $"DEON - {creation.EnvironmentName.ToUpperInvariant()}";


// this one gives the 401
var creationResponse = await $"https://management.azure.com/subscriptions/{authConfig.SubscriptionId}/resourceGroups/{creation.ResourceGroupName}/providers/Microsoft.AzureActiveDirectory/b2cDirectories/{creation.FullDirectoryName}?api-version=2019-01-01-preview"
                        .AllowAnyHttpStatus()
                        .PutJsonAsync(new
                        {
                            location = "Europe",
                            properties = new
                            {
                                createTenantProperties = new
                                {
                                    countryCode = creation.CountryCode,
                                    displayName
                                }
                            },
                            sku = new
                            {
                                name = "Standard",
                                tier = "A0"
                            }
                        });



Googling brought exactly zero pertinent or even just semi-pertinent hits. Now I'm stuck.


Solution

  • Based on the answer provided here: https://learn.microsoft.com/en-us/answers/questions/481152/creating-azure-b2c-tenant-via-rest-api-fails-with.html, it seems you cannot create an Azure AD B2C Tenant using a Service Principal (at least as of today).

    You will need to execute this request in context of an actual user. In other words, you will need to acquire a token to execute Service Management API with user_impersonation scope which is not possible with a Service Principal.