How do you get an MSAL token in a service/daemon app using client secret for StorageManagementClient?

Viewed 1256

What scope value do I use to obtain an auth token for a C# Desktop framework (4.8) app using an App Registration/Client Secret that works with the Azure Storage API? Or do I need to do something else? Following the MSAL sample, I have code like this:

var authorityUri = new Uri($"https://login.microsoftonline.com/{TenantId}");
var app = Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
    .Create(ApplicationId)
    .WithClientSecret(PrimaryClientSecretValue)
    .WithAuthority(authorityUri)
    .Build();
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };

AuthenticationResult result = null;
result = app.AcquireTokenForClient(scopes)
    .ExecuteAsync()
    .ConfigureAwait(false)
    .GetAwaiter()
    .GetResult();

var appSecretCredentials = new Microsoft.Rest.TokenCredentials(result.AccessToken, result.TokenType);
var storageManagementClient = new Microsoft.Azure.Management.Storage.StorageManagementClient(appSecretCredentials);
storageManagementClient.SubscriptionId = SubscriptionId;

var allStorageAccountsResponse = storageManagementClient.StorageAccounts
    .ListWithHttpMessagesAsync()
    .ConfigureAwait(false)
    .GetAwaiter()
    .GetResult();

var allStorageAccountsList = allStorageAccountsResponse.Body.ToArray();

Console.WriteLine("Storage account list");
foreach (var currStorageAccount in allStorageAccountsList)
    Console.WriteLine(currStorageAccount.Name);

This always fails with "Authentication Failed". I have tried the following scopes values with no improvement: https://management.azure.com, https://graph.microsoft.com/.default, https://graph.microsoft.com/.default, https://storage.azure.com, User.Read. Using multiple scopes throws earlier.

This WORKS - e.g., I can create storage accounts - if I instead using the following ADAL code in a .NET Core console app to get the token:

var clientCredential    = new ClientCredential(clientId, clientSecret);
var context             = new AuthenticationContext("https://login.windows.net/" + tenantId);
var result              = context.AcquireTokenAsync("https://management.azure.com/", clientCredential)
                                .ConfigureAwait(false)
                                .GetAwaiter()
                                .GetResult();

var appSecretCredentials = new Microsoft.Rest.TokenCredentials(result.AccessToken);
var storageManagementClient = new Microsoft.Azure.Management.Storage.StorageManagementClient(appSecretCredentials);
storageManagementClient.SubscriptionId = subscriptionId;
// ...remainder as above...

In the Azure Portal|Active Directory|App Registrations, under Authentication, Allow Public Client Flows is enabled and under API permissions, the app has defaults | Azure Storage\user_impersonation (the only permission available). In both cases I'm using Microsoft.Identity.Client 4.36.2.

2 Answers

For the standard client credentials flow use /.default. For example, https://graph.microsoft.com/.default. Azure AD will automatically include all the app-level permissions the admin has consented to in the access token for the client credentials flow. Scope to request access to all the Permissions. If you want to acquire a token for all the static scopes application, append .default to the app ID URI of the API:

ResourceId = "someAppIDURI";

var scopes = new [] {  ResourceId+"/.default"};

The scope should be:- scopes: ["https://management.core.windows.net/.default"]

You can also provide the sufficient permissions.

enter image description here

Read more here.

Thanks! Unfortunately the scope value was not it - I forgot to list all the scope strings I used and that was one. Nor was the Enterprise App permissions or consents. I did extensive testing of many things.

However, I did find the cause: the service principal needs to be granted Contributor rights on the parent resource group or target storage account (as you prefer). Since the SP already had Owner rights on the parent resource group, which permits the SP to create storage accounts, you would think the SP would be able to create blobs, containers, etc. within a storage account it creates in that RG. That is not the case. Being owner of a storage account should be sufficient rights for the SP to grant itself Contributor rights to solve this, but I have not yet written code to do that.

Related