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?
In my case, I registered one application and exposed an API with scope like this:
In API permissions tab, I added User.Read.All
permission of Application type that works with client credentials flow:
Now, I created another application named MyWpfApp
and granted exposed API permission as below:
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:
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: