Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal APIs in Blazor with RequireAuthorization() using Individual Account #420

Open
erossini opened this issue Jan 9, 2025 · 0 comments
Assignees
Labels
Not triaged Awaiting review

Comments

@erossini
Copy link

erossini commented Jan 9, 2025

In my .NET 8 Blazor project, I added the "Individual Account" to manage users. The application has 2 projects.

Server

The server side contains the login part and the controllers/endpoints.

enter image description here

The controllers/endpoints are minimal APIs like this one

public static void MapClientEndpoints (this IEndpointRouteBuilder routes)
{
    var group = routes.MapGroup("/api/Client").WithTags(nameof(Client));

    group.MapGet("/", async (HypnoContext db) =>
    {
        return await db.Client.ToListAsync();
    })
    .RequireAuthorization()
    .WithName("GetAllClients")
    .WithOpenApi();
}

in order to save in the database. Also, I added the configuration for HttpClient:

builder.Services.AddScoped<AuthenticationStateProvider, 
    PersistingRevalidatingAuthenticationStateProvider>();

builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies();

builder.Services.AddIdentityCore<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddSignInManager()
        .AddDefaultTokenProviders();

builder.Services
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("ServerAPI"))
        .AddHttpClient("ServerAPI", (provider, client) =>
        {
            client.BaseAddress = new Uri(builder. Configuration["FrontendUrl"]);
        });

Client

The client project has all the views. The views call the APIs provided by the server.

enter image description here

In the Program.cs this configuration is added out of the box:

builder.Services.AddSingleton<AuthenticationStateProvider, 
    PersistentAuthenticationStateProvider>();

In order to connect to the APIs from the pages, I created a class to group all the calls in a single place. The constructor is this

string baseEndpoint;
IHttpClientFactory ClientFactory;
HttpClient? httpClient;

public ApiService(string baseUrl, IHttpClientFactory clientFactory)
{
    baseEndpoint = baseUrl;
    ClientFactory = clientFactory;
    httpClient = ClientFactory.CreateClient("ServerAPI");
}

Although I log in to the application as a user, the calls to the APIs work only if no authorisation is required in the Minimal API. It is like that the IHttpClientFactory doesn't have the

How can I protect the APIs and use them from the client side?

What I tried

In the Program.cs of the server, I added this code for the HttpClient:

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("ServerAPI", client => {
        client.BaseAddress = new Uri(builder.Configuration["FrontendUrl"]);
    })
   .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

builder.Services.AddTransient(sp =>
    sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));

The CustomAuthorizationMessageHandler is as follows

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
           authorizedUrls: new[] { "https://localhost:7241" });
    }
}

I'm doing this because I want to make calls to the APIs exposed in the server project protected by the Individual Account. So, in a page

@inject HttpClient httpClient

HttpRequestMessage request = new HttpRequestMessage(
    HttpMethod.Get, "/api/Client");
await httpClient.SendAsync(request);

When I run the application, I immediately get an error:

System.AggregateException: 'Some services are not able to be
constructed (Error while validating the service descriptor
'ServiceType: MyApp.CustomAuthorizationMessageHandler
Lifetime: Scoped ImplementationType:
MyApp.CustomAuthorizationMessageHandler': Unable to resolve
service for type
'Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider'
while attempting to activate
'MyApp.CustomAuthorizationMessageHandler'.)'

enter image description here

Update

I added the configuration of IHttpClientFactory in the client project too.

builder.Services
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("ServerAPI"))
        .AddHttpClient("ServerAPI", (provider, client) =>
        {
            client.BaseAddress = new Uri(builder.Configuration["FrontendUrl"]);
        });

But the result has not changed. When from the UI, there is a call to the API, I always get 200 as an HTTP code but the content of the result is a login page.

enter image description here

Just to give you more info, all the calls are in a different project and I reference this project in the client project.

@erossini erossini added the Not triaged Awaiting review label Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not triaged Awaiting review
Projects
None yet
Development

No branches or pull requests

2 participants