Search code examples
asp.net-coreidentityserver4blazor-webassembly

Deployed WebAssembly Blazor application doesn't route authentication properly, but locally working


I created a 'normal' WebAssembly Blazor client and server application.

I decided later on to add authentication, so I followed the steps at this address:

https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-3.1&tabs=visual-studio

ending up with this startup code in the server part of the Blazor WebAssembly application:

public class Startup
{
    private readonly IWebHostEnvironment _environment;
    private readonly IConfiguration _configuration;

    public Startup(IWebHostEnvironment environment, IConfiguration configuration)
    {
        _environment = environment;
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        if (_environment.IsDevelopment())
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    _configuration.GetConnectionString("LocalEnvironment")));
        }
        else
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    _configuration.GetConnectionString("CloudEnvironment")));
        }

        services.AddDefaultIdentity<ApplicationUser>()
            .AddRoles<ApplicationRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddIdentityServer()
            .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
            {
                options.IdentityResources["openid"].UserClaims.Add("name");
                options.ApiResources.Single().UserClaims.Add("name");
                options.IdentityResources["openid"].UserClaims.Add("role");
                options.ApiResources.Single().UserClaims.Add("role");
            });

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

        services.AddAuthentication()
            .AddIdentityServerJwt();

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

        services.Configure<IdentityOptions>(options =>
        {
            options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;

            options.User.RequireUniqueEmail = true;

            options.Password.RequiredLength = 8;
            options.Password.RequireNonAlphanumeric = true;
            options.Password.RequireLowercase = false;
            options.Password.RequireUppercase = true;
            options.Password.RequireDigit = true;
        });

        services.AddTransient<IPasswordValidator<ApplicationUser>, CustomPasswordPolicy>();
        services.AddTransient<IUserValidator<ApplicationUser>, CustomUsernameEmailPolicy>();

        services.AddTransient<IProfileService, ProfileService>();

        services.AddHttpContextAccessor();

        services.AddHsts(options =>
        {
            options.Preload = true;
            options.IncludeSubDomains = true;
            options.MaxAge = TimeSpan.FromDays(60);
        });
    }

    public void Configure(IApplicationBuilder app, ApplicationDbContext db,
        UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager)
    {
        if (_environment.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseWebAssemblyDebugging();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        db.Database.EnsureCreated();

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

        app.UseRouting();

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

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
            endpoints.MapControllers();
            endpoints.MapFallbackToFile("index.html");
        });

        IdentityDataInitializer.SeedTestData(userManager, roleManager);
    }
}

The resulting application works perfectly when in the Development environment (both in Kestrel and IIS Express, but when I deploy it to an Azure Service App, the authentication part, and only that one, doesn't work properly.

For example: if I click the Login button in the home page, when I'm local there's a jump to the page:

https://localhost:5001/Identity/Account/Login?ReturnUrl=...

That's the correct path, because, moreover, after logging in, I'm redirected correctly to the home page.

But when I click the same button on the deployed application, I see the address becoming first

'.../authentication/login'

and after a few moments, going to

'.../connect/authorize?client_id=Test1.Client&redirect_uri=...'

that's a not existing page.

Personally, I don't even understand, at the moment, if it's a server or client problem, or just the configuration of the service app on Azure...

Please, feel free to ask for other code, or anything that can help.

Thank you in advance.


Solution

  • /connect/authorize is one of the endpoints that IdentityServer listens for and it is the first URL that the application/client should redirect to when the user is about to authenticate.

    One way to tell if IdentityServer is up and running is to go to this URL https://yourdomain.com/.well-known/openid-configuration

    This URL Should always succeed.

    When you deploy to the cloud and Azure Service App, one thing is to make sure you understand where HTTPS is terminated, is it in your application or in Azure Service? If it is not terminated in the application it-self, then it might be that the public URL is HTTPS but what your application sees is HTTP.

    Some links to follow: