Steve Greene
Steve Greene

Reputation: 12304

Blazor server with web api controller authenticate issue

I have a Blazor server app that I want to add a web api controller to that can be accessed from Postman and eventually other apps. The Blazor app needs authentication, but not the web api. I tried adding AllowAnonymous, but I am getting an authentication error calling it from Postman:

HTTP Error 401.2 - Unauthorized You are not authorized to view this page due to invalid authentication headers.

I suspect our security proxy is adding the headers:

enter image description here

Is it possible to host an unsecured (AllowAnonymous) web api inside an authenticated Blazor Server app?

Maybe I just need to craft my api call a certain way?

Controller:

[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ProfileController : ControllerBase
{
    [HttpGet("{year}", Name = "GetProfileResults")]
    public async Task<IActionResult> GetProfileResults(int year)
    {
        var profileResults = repo.GetResults(year);
        return Ok(profileResults);
    }
}

Upvotes: 0

Views: 2232

Answers (2)

alex.z
alex.z

Reputation: 104

The key point to host a public API in a Blazor Server app is to ensure the API routing takes precedence over others.

In Program.cs (or Startup.cs):

    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();

    app.MapControllers(); // the order is important, this ensures API takes precedence.
    app.MapBlazorHub();
    app.MapFallbackToPage("/_Host");

    app.Run();

Alternatively for endpoint routing:

    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        app.MapControllers(); // the order is important, this ensures API takes precedence.
        app.MapBlazorHub();
        app.MapFallbackToPage("/_Host");
    });

    app.Run();

Next, the controller. In your example the code is completely correct. It must use [AllowAnonymous] at the controller level or at specific actions as usual.

[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ProfileController : ControllerBase
{
    [HttpGet("{year}", Name = "GetProfileResults")]
    public async Task<IActionResult> GetProfileResults(int year)
    {
        var profileResults = repo.GetResults(year);
        return Ok(profileResults);
    }
}

That should be enough to route the call to API before Blazor takes over the security.

Last but not the least is the default exception configuration handling code added to Blazor projects by default:

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

Please be aware that when this code is used any unhandled exceptions during an API call will be caught by the error handler which doesn't respect API [AllowAnonymous] settings and may trigger the authentication challenge configured for Blazor.

Upvotes: 2

Brian Parker
Brian Parker

Reputation: 14523

You have to add another http client with no tokens attached.

Program.cs

builder.Services.AddHttpClient(
    name: "Anon.ServerAPI",
    client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

RazorPage.razor.cs

[Inject]
public IHttpClientFactory HttpClientFactory { get; set; }

protected override async Task OnInitializedAsync()
{
    http = HttpClientFactory.CreateClient("Anon.ServerAPI");
    videos = await http.GetFromJsonAsync<VideoDto[]>("api/YoutubeVideos");
}

Upvotes: 2

Related Questions