Reputation: 160
I have a C# Blazor server app running on net7.0 framework where I'm using AzureAD user account and then I communicating with Azure REST API (https://management.azure.com/)
When I run this app in Visual Studio with IIS Express, everything works fine. But when I publish it on the IIS server, there is a problem with the authentication. I can log in and i can see my user login but when my class tries to call Azure API with IServiceProvider and IDownstreamWebApi, it cause circular redirect to https://login.microsoftonline.com and ends up with an error.
User identity is null (on IIS) in LoginDisplay.razor when i want to take it with ContextAccessor (but on IIS express are both filled):
<AuthorizeView>
<Authorized>
Context: "@context.User.Identity?.Name"
ContextAccessor: "@httpContextAccessor.HttpContext?.User.Identity?.Name"
<a href="MicrosoftIdentity/Account/SignOut">@Loc["LogOut"]</a>
</Authorized>
<NotAuthorized>
<a href="MicrosoftIdentity/Account/SignIn">@Loc["LogIn"]</a>
</NotAuthorized>
</AuthorizeView>
Program.cs:
var builder = WebApplication.CreateBuilder(args);
var initialScopes = builder.Configuration["ManagementApi:Scopes"]?.Split(' ');
builder.Configuration.AddJsonFile("MyAppAzureManagerOptions.json",optional: false,reloadOnChange: true);
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddDownstreamWebApi("ManagementAPI", builder.Configuration.GetSection("ManagementAPI"))
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<AzureApiController>()
.AddTokenAcquisition(true);
builder.Services.AddSingleton<MyAppAPIController>();
builder.Services.AddSingleton<MyAppAzureManagerServices>();
builder.Services.AddLocalization();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
builder.WebHost.UseWebRoot("wwwroot");
builder.WebHost.UseStaticWebAssets();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
AzureApiController:
public class AzureApiController
{
IConfiguration _configuration;
private readonly IServiceProvider _serviceProvider;
ILogger<AzureApiController> _logger;
public AzureApiController(IConfiguration configuration, IServiceProvider serviceProvider,ILogger<AzureApiController> logger)
{
_configuration = configuration;
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task<string> TestGetAppsAsync()
{
using (var scope = _serviceProvider.CreateScope())
{
var _downstreamAPI = scope.ServiceProvider.GetRequiredService<IDownstreamWebApi>();
HttpResponseMessage response = await _downstreamAPI.CallWebApiForUserAsync(
"ManagementAPI",
options => options.RelativePath = $"subscriptions/{_configuration["AppSettings:Subscription"]}/resourceGroups/{_configuration["AppSettings:ResourceGroupNameApp"]}/providers/Microsoft.Web/sites?api-version=2021-02-01");
return await response.Content.ReadAsStringAsync();
}
}
}
Network in DevTools after fetch action from API:
Isn't my Program.cs auth part wrong? Why it works in VS but not in IIS? When I want to use HttpClient with bearer header I get same result when getting current token.
EDIT:
When I don't use IServiceProvider
and use solution based on https://github.com/Azure-Samples/ms-identity-blazor-server/tree/main/WebApp-graph-user/Call-MSGraph where I pass bearer header into HttpClient
instance of injected IHttpClientFactory
, it works. But still, previous solution looked cleaner, so is there any chance to get working with IDownstreamWebApi
?
Upvotes: 0
Views: 616
Reputation: 10839
Try change the code to use ITokenAquisition implementation where you add new ExtraHttpHeaders
which is of IHeaderDictionary to pass calls to MSAL.NET. The ExtraHttpHeaders can be used in DownstreamWebApiOptions to then call web api.
But To call an API other than the Microsoft graph API, using the helper class IDownstreamWebApi only , you can use customize method to override the Http headers .
Code from adding call api to web app·GitHub mentioned by @Jean-Marc Prieur
public async Task<ActionResult> Details(int id)
{
var value = await _downstreamWebApi.GetForUser<string>("DownstreamAPI",
options => {
options.RelativePath = $"subscriptions/{_configuration["AppSettings:Subscription"]}/resourceGroups/{_configuration["AppSettings:ResourceGroupNameApp"]}/providers/Microsoft.Web/sites?api-version=2021-02-01";
options.CustomizeHttpRequestMessage = message =>
{
...
var headers = message.Headers;
...
//Headers will be populated
};
});
Or
//var value = await _downstreamWebApi.CallWebApiForUserAsync<object, Todo>(
// ServiceName,
// null,
// options =>
// {
// options.HttpMethod = HttpMethod.Get;
// options.RelativePath = $"api/todolist/{id}";
// });
// return View(value);
}
Reference : Call a web api from a web app - Microsoft Entra | Microsoft Learn
Upvotes: 0