Search code examples
c#asp.net-coreasp.net-identityidentityserver4blazor-webassembly

Problem with Authentification in Blazor Webassembly project which hosted on ASP.NET Core project


I have two projects:

Notes.Server - ASP.NET Core project,
Notes.Client - Blazor WASM project

My Note App contain Client.cshtml page which have:

<component type="typeof(PrettyNotes.Web.Client.App)" render-mode="WebAssemblyPrerendered"/>

It's preprenderes Blazor WASM. I implementing oidc authentification in this prerendered project.

Notes.Server's Startup.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PrettyNotes.Web.Server.Models;
using PrettyNotes.Web.Server.Models.Data;

namespace Notes.Web.Server
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddRazorPages().AddRazorRuntimeCompilation();
            services.AddDbContext<PrettyNotesApplicationDBContext>(options =>
            {
                options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ProjectDB;Trusted_Connection=True;");
            });

            services.AddIdentity<PNUser, IdentityRole>(options =>
            {
                options.Password.RequiredLength = 8;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireDigit = true;
            }).AddEntityFrameworkStores<PrettyNotesApplicationDBContext>();
            services.AddAuthentication().AddIdentityServerJwt();

            services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme,
                opt =>
                {
                    opt.LoginPath = "/auth/login";
                    opt.LogoutPath = "/auth/logout";
                });

            services.AddIdentityServer()
                .AddInMemoryApiResources(new ApiResource[] { new ApiResource("API", "ServerAPI") }).AddInMemoryClients(
                    new[]
                    {
                        new IdentityServer4.Models.Client
                        {
                            ClientId = "PrettyNotesClient",
                            AllowedGrantTypes = GrantTypes.Code,
                            RequirePkce = true,
                            RequireClientSecret = false,
                            AllowedCorsOrigins = { "https://localhost:5001" },
                            AllowedScopes = { "openid", "profile", "email" },
                            RedirectUris = { "https://localhost:5001/client/security/auth/login-callback" },
                            PostLogoutRedirectUris = { "http://localhost:5001/client" },
                            Enabled = true
                        }
                    })
                .AddInMemoryIdentityResources(
                    new IdentityResource[]
                    {
                        new IdentityResources.Address(),
                        new IdentityResources.Profile(),
                        new IdentityResources.Email(),
                        new IdentityResources.OpenId()
                    }).AddInMemoryApiScopes(new[] { new ApiScope("API") }).AddAspNetIdentity<PNUser>()
                .AddDeveloperSigningCredential();
            services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>().AddScoped<SignOutSessionStateManager>();
            services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseStaticFiles();
            app.UseBlazorFrameworkFiles();

            app.UseAuthentication();
            app.UseAuthorization();
            app.UseIdentityServer();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapRazorPages();
                endpoints.MapFallbackToController("/client/{**segment}", "Index", "Client");
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

My Blazor WASM Project is prerendered in server. When I trying to reach Blazor WASM Project page that prerenders Blazor, I get next error:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidCastException: 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]'.

How to resolve this problem? Can you suggest some resources, repos about Authentification in Prerendered Blazor Webassembly?


Solution

  • The documentation here should help already a lot: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-6.0#support-prerendering-with-authentication

    although they seem to ignore the fact that the callback-login url stays stuck on "loading" as you need to disable the div override for prerendering, still figuring that on out