Pritha Kundu
Pritha Kundu

Reputation: 5

Not able to login to Azure using service principal in C#

While running below Azure CLI command, I am able to login and perform Azure operations. Please note the application is not attached to any role, so I am passing --allow-no-subscriptions here.

az login --service-principal --username $app_id --password $password 
         --tenant $tenant_id --allow-no-subscriptions

However when I try to run the same command in this C# code, I am getting an error:

services.AddTransient(x =>
{
    var options = x.GetService<IOptions<SynchronizationOptions>>().Value;
    var credentials = new ClientSecretCredential(options.AzureWorkItemSettings.tenantId, options.AzureWorkItemSettings.clientId, options.AzureWorkItemSettings.clientSecret);
    var accessToken = credentials.GetToken(new Azure.Core.TokenRequestContext(new[] { options.AzureWorkItemSettings.azureDevopsAppScope }));
    var vssAadToken = new VssAadToken("Bearer", accessToken);
    var vssAadCredentials = new VssAadCredential(vssAadToken);
    VssConnection connection = new VssConnection(options.AzureWorkItemSettings.Uri, vssAadCredentials);
    return connection.GetClient<WorkItemTrackingHttpClient>();
});

Here is the error - it seems it's because --allow-no-subscriptions is not passed in the C# code, but I am not able to see any option where I could pass --allow-no-subscriptions from my C# code. Please let me know how to fix this error?

Unhandled exception. Azure.Identity.AuthenticationFailedException: ClientSecretCredential authentication failed:

MSAL. NetCore.4.67.2.0.MsalUiRequiredException:
ErrorCode: invalid_grant
Microsoft.Identity.Client.MsalUiRequiredException: AADSTS501051: Application 'xxxxxxxxx' is not assigned to a role for the application 'xxxxxxxxx'.

Upvotes: 0

Views: 86

Answers (1)

Rukmini
Rukmini

Reputation: 16064

Note : az login CLI command doesn't have this issue because it allows no subscriptions to be required (--allow-no-subscriptions). This functionality isn't exposed in the ClientSecretCredential API and hence the error.

To use ClientSecretCredential flow to access Azure DevOps, you need to add the service principal to the Azure DevOps organization. Refer this MsDoc

Hence to resolve the error, Go to Azure DevOps Portal > Organization Settings > Permissions. Find your Service Principal (under Security or User section) and add it to an appropriate group (e.g., Project Administrators, Contributors, or a custom security group with sufficient access:

enter image description here

Now I used the below code to used to query work items:

Make sure to pass scope as 499b84ac-1321-427f-aa17-267ca6975798/.default

public class Program
{
    public static void Main(string[] args)
    {
        // Set up Dependency Injection (DI) container
        var serviceProvider = new ServiceCollection()
            .Configure<SynchronizationOptions>(options =>
            {
                options.AzureWorkItemSettings = new AzureWorkItemSettings
                {
                    TenantId = "TenantID",  
                    ClientId = "ClientID",  
                    ClientSecret = "Secret",  
                    AzureDevOpsOrg = "rukorg", 
                    AzureDevOpsAppScope = "499b84ac-1321-427f-aa17-267ca6975798/.default",  
                    Uri = new Uri("https://dev.azure.com/rukorg")  
                };
            })
            .AddTransient(x =>
            {
                var options = x.GetService<IOptions<SynchronizationOptions>>().Value;
                var credentials = new ClientSecretCredential(options.AzureWorkItemSettings.TenantId, options.AzureWorkItemSettings.ClientId, options.AzureWorkItemSettings.ClientSecret);
                var accessToken = credentials.GetToken(new TokenRequestContext(new[] { options.AzureWorkItemSettings.AzureDevOpsAppScope }));
                var vssAadToken = new VssAadToken("Bearer", accessToken.Token);
                var vssAadCredentials = new VssAadCredential(vssAadToken);
                VssConnection connection = new VssConnection(options.AzureWorkItemSettings.Uri, vssAadCredentials);
                return connection.GetClient<WorkItemTrackingHttpClient>();
            })
            .BuildServiceProvider();

        // Get the WorkItemTrackingHttpClient from DI container
        var workItemTrackingClient = serviceProvider.GetService<WorkItemTrackingHttpClient>();

        // Call your method to query work items
        var workItemService = new WorkItemService(workItemTrackingClient);
        workItemService.GetWorkItemsAsync().Wait();
    }
}

public class WorkItemService
{
    private readonly WorkItemTrackingHttpClient _workItemTrackingClient;

    public WorkItemService(WorkItemTrackingHttpClient workItemTrackingClient)
    {
        _workItemTrackingClient = workItemTrackingClient;
    }

    public async Task GetWorkItemsAsync()
    {
        // Define your WIQL query to fetch work items
        var wiql = new Wiql
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] FROM WorkItems"
        };

        // Execute the WIQL query to get work items
        var result = await _workItemTrackingClient.QueryByWiqlAsync(wiql);

        Console.WriteLine("Work Items:");
        foreach (var workItemReference in result.WorkItems)
        {
            // Fetch the full details of each work item
            var workItemDetails = await _workItemTrackingClient.GetWorkItemAsync(workItemReference.Id);
            Console.WriteLine($"ID: {workItemDetails.Id}, Title: {workItemDetails.Fields["System.Title"]}, State: {workItemDetails.Fields["System.State"]}");
        }
    }
}

public class AzureWorkItemSettings
{
    public string TenantId { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public string AzureDevOpsOrg { get; set; }
    public string AzureDevOpsAppScope { get; set; }
    public Uri Uri { get; set; }
}

public class SynchronizationOptions
{
    public AzureWorkItemSettings AzureWorkItemSettings { get; set; }
}

enter image description here

  • I have not added any role to the Microsoft Entra ID application under the subscription.

Upvotes: 0

Related Questions