StewieG
StewieG

Reputation: 1200

.NET 5 Blazor ServerSide AND WebAssembly (hybrid) with WebAPI Authentication?

Is there a way to use the default Authentication (.NET 5 / Identity-Server) with Blazor ServerSide and Blazor WebAssembly in a hybrid manner? I want to have a Blazor Project, which can be switched between ClientSide (WebAssembly) and ServerSide, to keep the Client the same, I want to use the WebAPI on ClientSide and ServerSide. I started with ServerSide (better debugging and better performance for tables *) and maybe switch to ClientSide later (if the performance will be better).

*Please do not start discussing about what is better or if YOU maybe have a good performance with WebAssembly, then you don't have many nested components like component based tables.

I did test so many combinations, but can't get it to work, but let's start with the basic:

Startup.cs (Server)

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    services.AddAuthentication().AddIdentityServerJwt();
    
    // SERVER SIDE SUPPORT
    services.AddServerSideBlazor();
    services.AddApiAuthorization();
    if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
    {
        services.AddScoped(s =>
        {
            var uriHelper = s.GetRequiredService<NavigationManager>();
            return new HttpClient
            {
                BaseAddress = new System.Uri(uriHelper.BaseUri)
            };
        });
    }
    // --

    services.AddControllersWithViews();
    services.AddRazorPages();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
        app.UseWebAssemblyDebugging();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseBlazorFrameworkFiles();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseIdentityServer();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllers();
        
        // SERVER-SIDE SUPPORT
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host"); //endpoints.MapFallbackToFile("index.html");
        // --
    });
}

Program.cs (Client)

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    //builder.RootComponents.Add<App>("#app");

    builder.Services.AddHttpClient("BlazorSwitchWithSec2.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
        .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

    // Supply HttpClient instances that include access tokens when making requests to the server project
    builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("BlazorSwitchWithSec2.ServerAPI"));

    builder.Services.AddApiAuthorization();

    await builder.Build().RunAsync();
}

_Host.cshtml (Server)

@page "/"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        @if (Request.QueryString.Value.ToLower().Contains("mode=client"))
        {
            <title>WebApp (CE)</title>
        }
        else
        {
            <title>WebApp (SE)</title>
        }
        <base href="~/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/app.css" rel="stylesheet" />
    </head>

    <body>
        @if (Request.QueryString.Value.ToLower().Contains("mode=client"))
        {
            <component type="typeof(BlazorSwitchWithSec2.Client.App)" render-mode="WebAssemblyPrerendered" />
            <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
            <script src="_framework/blazor.webassembly.js"></script>
            <script>navigator.serviceWorker.register('service-worker.js');</script>
        }
        else
        {
            <component type="typeof(BlazorSwitchWithSec2.Client.App)" render-mode="Server" />
            <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
            <script src="_framework/blazor.server.js"></script>
        }
    </body>
</html>

If I try to register or login via WebAssembly or Server, I get this error: Unable to cast object of type 'Microsoft.AspNetCore.Components.Server.ServerAuthenticationStateProvider' to type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.IRemoteAuthenticationService`1[Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationState]'

Maybe also this helps: I tried something different like using the services from the client, one combination seemed to work with controllers without [Authorize] attribute, but with it, I got a error like Unable to parse ... - the result was a login url (this is why the parsing didn't work). I can't post the code, because I tested too much and the project isn't working anymore.

Without any Authentication everything works and I could send a token to the WebAPI to create my own login system. But I want to try the default one.

Upvotes: 2

Views: 1484

Answers (2)

Mostafa Zaki
Mostafa Zaki

Reputation: 31

I don’t like the approach of BlazorServer referencing BlazorWasm. I prefer to have both BlazorServer and BlazorWasm projects reference a dll(rdl) which contains the App, makes it a bit more clear that both are using a shared codebase and it avoids BlazorServer pulling in unnecessary dependencies from BlazorWasm. you can check this link https://github.com/mostafaefcih/BlazorDaulMode or check blazor getting started course on plaursight

Upvotes: 1

DotNetDublin
DotNetDublin

Reputation: 798

These Blazor Train videos from Carl Franklin may help you.

Blazor Synchronicity; Develop Server & WASM Apps Simultaneously

https://www.youtube.com/watch?v=SkYQDPXw__c

Blazor Synchronicity 5.0

https://www.youtube.com/watch?v=fHzIWOfmqzg

Upvotes: 0

Related Questions