Madhura D
Madhura D

Reputation: 5

How to get Users' names list from azure AD app in a WPF application?

I am building a WPF application, and I want to retrieve the names of all users from an Azure AD app when a button is clicked or in a dropdown. I have written an HttpGet method in an ASP.NET Core Web API to achieve this, and I have tested it using Postman (with OAuth 2.0), where I can successfully see the names of all users. However, when I call the HttpGet endpoint from the WPF application, I am encountering errors.

This is my HttpGet endpoint, which returns all users' names from the Azure AD app when tested in Postman (OAuth 2.0 where I have provided client ID, client secret, scope, etc) -

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
    private readonly ITokenAcquisition _tokenAcquisition;
    private readonly ILogger<UsersController> _logger;

    public UsersController(ITokenAcquisition tokenAcquisition, ILogger<UsersController> logger)
    {
        _tokenAcquisition = tokenAcquisition;
        _logger = logger;
    }

    [HttpGet]
    public async Task<IActionResult> GetUsers()
    {
        try
        {
            var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { "User.ReadBasic.All", "User.Read" });
            var graphClient = new GraphServiceClient(new AuthProvider(token));

            var usersList = new List<string>();
            var usersPage = await graphClient.Users
                .Request()
                .Select(u => new { u.DisplayName })
                .GetAsync();

            usersList.AddRange(usersPage.Select(u => u.DisplayName));

            while (usersPage.NextPageRequest != null)
            {
                usersPage = await usersPage.NextPageRequest.GetAsync();
                usersList.AddRange(usersPage.Select(u => u.DisplayName));
            }

            return Ok(usersList);

        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An error occurred while fetching users.");
            return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while fetching users.");
        }
    }

    private class AuthProvider : IAuthenticationProvider
    {
        private readonly string _token;
        public AuthProvider(string token)
        {
            _token = token;
        }
        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
            await Task.CompletedTask;
        }
    }
}

Calling API in my WPF app (I am not sure whether I am using right approach)-

public partial class MainWindow : Window
{
    private static readonly HttpClient client = new HttpClient();
    private static IConfidentialClientApplication confidentialClientApp;

    public MainWindow()
    {
        InitializeComponent();

        // Initialize MSAL ConfidentialClientApplication with client secret
        confidentialClientApp = ConfidentialClientApplicationBuilder.Create("47......")
            .WithClientSecret("gF......")
            .WithAuthority(AzureCloudInstance.AzurePublic, "d81e......")
            .WithRedirectUri("http://localhost")
            .Build();
    }

    private async void FetchDataButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            // Acquire token silently
            string[] scopes = new string[] { "api://47....../.default" };
            AuthenticationResult authResult = await confidentialClientApp.AcquireTokenForClient(scopes).ExecuteAsync();

            // Set the token in the request header
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

            // Make the HTTP GET request
            string apiUrl = "https://localhost:7159/api/Users";
            var response = await client.GetAsync(apiUrl);

            if (response.IsSuccessStatusCode)
            {
                string responseData = await response.Content.ReadAsStringAsync();
                MessageBox.Show(responseData, "API Response", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            else
            {
                MessageBox.Show("Error: " + response.ReasonPhrase, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        catch (MsalException msalEx)
        {
            MessageBox.Show("Authentication error: " + msalEx.Message, "Authentication Error", MessageBoxButton.OK, MessageBoxImage.Error);
        }
        catch (Exception ex)
        {
            MessageBox.Show("Exception: " + ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }
}

But I am getting an error - MsalUiRequiredException: AADSTS50058: A silent sign-in request was sent but no user is signed in.

What can I do to call this HttpGet endpoint & display users names list in my WPF app?

Upvotes: 0

Views: 115

Answers (1)

Sridevi
Sridevi

Reputation: 22562

In my case, I registered one application and exposed an API with scope like this:

enter image description here

In API permissions tab, I added User.Read.All permission of Application type that works with client credentials flow:

enter image description here

Now, I created another application named MyWpfApp and granted exposed API permission as below:

enter image description here

Please find below code samples that worked in my case:

UsersController.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using Microsoft.Identity.Web;
using Microsoft.Kiota.Abstractions.Authentication;
using Microsoft.Kiota.Abstractions;

[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
    private readonly ITokenAcquisition _tokenAcquisition;

    public UsersController(ITokenAcquisition tokenAcquisition)
    {
        _tokenAcquisition = tokenAcquisition;
    }

    [HttpGet]
    public async Task<IActionResult> GetUsers()
    {
        try
        {
            var token = await _tokenAcquisition.GetAccessTokenForAppAsync("https://graph.microsoft.com/.default");
            var graphClient = new GraphServiceClient(new AuthProvider(token));

            // Fetch users with displayName
            var users = await graphClient.Users.GetAsync((requestConfiguration) =>
            {
                requestConfiguration.QueryParameters.Select = new string[] { "displayName" };
            });

            if (users == null || users.Value == null)
            {
                return StatusCode(500, "Error retrieving users: Users response is null");
            }

            var usersList = users.Value
                .Where(u => u.DisplayName != null)
                .Select(u => u.DisplayName)
                .ToList();

            return Ok(usersList);
        }
        catch (System.Exception ex)
        {
            return StatusCode(500, $"Error retrieving users: {ex.Message}");
        }
    }

    private class AuthProvider : IAuthenticationProvider
    {
        private readonly string _token;

        public AuthProvider(string token)
        {
            _token = token;
        }

        public Task AuthenticateRequestAsync(RequestInformation request, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default)
        {
            request.Headers.Add("Authorization", $"Bearer {_token}");
            return Task.CompletedTask;
        }
    }
}

When I ran the above API, I'm able to retrieve the users successfully by calling below endpoint:

GET https://localhost:7261/api/Users

Response:

enter image description here

To get the same response in WPF app when button is clicked, you can make use of below sample code files:

MainWindow.xaml:

<Window x:Class="MyWpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="My WPF App" Height="350" Width="525">
    <Grid>
        <Button Name="FetchDataButton" Content="Fetch Users" Click="FetchDataButton_Click" Width="150" Height="40" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" />
        <ListBox Name="UsersListBox" Width="400" Height="200" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,60,0,0" />
    </Grid>
</Window>

MainWindow.xaml.cs:

using Microsoft.Identity.Client;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Windows;

namespace MyWpfApp
{
    public partial class MainWindow : Window
    {
        private static readonly HttpClient client = new HttpClient();
        private static IConfidentialClientApplication confidentialClientApp;  

        public MainWindow()
        {
            InitializeComponent();
            
            confidentialClientApp = ConfidentialClientApplicationBuilder.Create("wpfappId")
                .WithClientSecret("wpfappsecret")
                .WithAuthority(new Uri("https://login.microsoftonline.com/tennatId"))
                .Build();
        }

        private async void FetchDataButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                string[] scopes = { "api://myuserapiappId/.default" };
                AuthenticationResult authResult = await confidentialClientApp.AcquireTokenForClient(scopes).ExecuteAsync();

                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

                string apiUrl = "https://localhost:7261/api/Users";
                var users = await client.GetFromJsonAsync<string[]>(apiUrl);

                UsersListBox.Items.Clear();
                if (users != null)
                {
                    foreach (var user in users)
                    {
                        UsersListBox.Items.Add(user);
                    }
                }
                else
                {
                    MessageBox.Show("No users found.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            catch (MsalException msalEx)
            {
                MessageBox.Show("Authentication error: " + msalEx.Message, "Authentication Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
    }
}

Response:

enter image description here

Upvotes: 0

Related Questions