question

DerDerErIst avatar image
0 Votes"
DerDerErIst asked BrandoZhang-MSFT commented

Blazor WASM on Windows 2016 Server Hosted with IIS, Authentication Token failed

Hello, I'm relatively new to Blazor

Here is a whole Blog Post about it and my Server Settings and everything:
https://stusse.de/setup-windows-server-2016-with-blazor-webassembly-app-authentication-failed-part-1/

Short Info:

I Wanna create a Blazor WASM for my Business. So it handles Highscorelists for my Developed Games as well as storing User Data and Game Informations, which also should be managed and accessed online through a Web interface.

What Is the Problem?

I'm Running the standard Blazor WASM Core hosted with Integrated User Accounts Template on my Windows 2016 Server with IIS Here is a link to the Live Site - Stusse Server. But for some reason, I don't understand, the Login, as well as Redirection, seems not to work properly and the Client Side of the Blazor WASM loses the Connect Token.

Testing:

I have tested the same Blazor WASM Template running locally on my Personal Computer as well as on the Server running locally without any issues. It's only once I Publish the Blazor WASM to my IIS Server.

The only changes I've made to the Blazor WASM Template are here in my AppSettings.json, to implement a Key and the Certificate and also the correct Connection String which works, how I said, locally just fine but not in the Live Environment.

{ "ConnectionStrings": { "DefaultConnection": "Server=XXX;Database=XXX;User ID = XXX; Password = XXX;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "IdentityServer": { "Clients": { "StusseServer.Client": { "Profile": "IdentityServerSPA" } }, "Key": { "Type": "Store", "StoreName": "My", "StoreLocation": "LocalMachine", "Name": "CN=XXX" } }, "AllowedHosts": "*" }

Now my Questions:

Are there Roles or Features from the Windows Server I might need to add for the Live Environment?
Or any IIS Modul I need? Some other Server Configurations I have to make?
Or do I miss some Settings in the App so the App can work in a Live Environment?

The Windows Server is a kinda Fresh Installation, with Standart Web Server Configurations, IIS Server, and .Net Frameworks
It contains a MSSQL with MSSMS, Visual Studio, Core SDK, Net SDK, Core Hosting Bundle


I hope someone can help me figure this out. I'm testing and searching already long, to fix that problem but I don't find an answer to my problem. If you need additional pieces of information feel free to ask.

Edit:
Some Additions When you are Logged in and Try to Access Pages on the App:
![104809-grafik.png][1]
[1]: /answers/storage/attachments/104809-grafik.png

If you Click on Register once you logged in you get routed to the Register Page and you can Access the Profile
![104790-grafik.png][1]
[1]: /answers/storage/attachments/104790-grafik.png

But on Client-Side the Token seems not to be available anymore
![104840-grafik.png][1]
[1]: /answers/storage/attachments/104840-grafik.png

Adding Code:
launchSettings.Json

 {
   "iisSettings": {
     "windowsAuthentication": false,
     "anonymousAuthentication": true,
     "iis": {
       "applicationUrl": "https://www.stusse-server.com:14424",
       "sslPort": 44320
     }
   }
 }

AppSettings.Json

 {
   "ConnectionStrings": {
     "DefaultConnection": "Server=xxx;Database=xxx;User ID = xxx; Password = xxx;MultipleActiveResultSets=true"
   },
   "Logging": {
     "LogLevel": {
       "Default": "Information",
       "Microsoft": "Warning",
       "Microsoft.Hosting.Lifetime": "Information"
     }
   },
   "IdentityServer": {
     "Authority": "https://www.stusse-server.com",
     "ResponseType": "authorization_code",
     "DefaultScopes": [
       "openid",
       "profile"
     ],
     "PostLogoutRedirectUri": "https://www.stusse-server.com/authentication/logout-callback",
     "RedirectUri": "https://www.stusse-server.com/authentication/login-callback",
     "Clients": {
       "StusseServer.Client": {
         "Profile": "IdentityServerSPA"
       }
     },
     "Key": {
       "Type": "Store",
       "StoreName": "My",
       "StoreLocation": "LocalMachine",
       "Name": "CN=stusse-server.com"
     }
   },
   "Authentication": {
     "Google": {
       "ClientId": "xxx.apps.googleusercontent.com",
       "ClientSecret": "xxx",
       "ResponseType": "authorization_code"
     }
   },
   "AllowedHosts": "*"
 }

Client - Program.cs

 public class Program
     {
         public static async Task Main(string[] args)
         {
             var builder = WebAssemblyHostBuilder.CreateDefault(args);
             builder.RootComponents.Add<App>("app");
    
             builder.Services.AddHttpClient("StusseServer.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("StusseServer.ServerAPI"));
    
             builder.Services.AddApiAuthorization()
                 .AddAccountClaimsPrincipalFactory<CustomUserFactory>();
    
             builder.Services.AddOidcAuthentication(options =>
             {
                 builder.Configuration.Bind("IdentityServer", options.ProviderOptions);
             });
    
             await builder.Build().RunAsync();
         }
     }

And the Startup.cs

 public class Startup
     {
         public Startup(IConfiguration configuration)
         {
             Configuration = configuration;
         }
    
         public IConfiguration Configuration { get; }
    
         // This method gets called by the runtime. Use this method to add services to the container.
         // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
         public void ConfigureServices(IServiceCollection services)
         {
             services.AddDbContext<ApplicationDbContext>(options =>
                 options.UseSqlServer(
                     Configuration.GetConnectionString("DefaultConnection")));
    
             services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
                 .AddRoles<IdentityRole>()
                 .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");
                 });
    
             services.AddTransient<IProfileService, ProfileService>();
    
             JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
    
             services.AddCors(options =>
             {
                 options.AddDefaultPolicy(builder =>
                     // TODO: this should be limited only to specified sources
                     builder.WithOrigins("https://www.stusse-server.com:44320")
                             .AllowAnyMethod()
                             .AllowAnyHeader()
                             .AllowCredentials());
             });
    
             services.AddAuthentication()                
             .AddIdentityServerJwt()            
                 .AddGoogle(google =>
                 {
                     google.ClientId = Configuration["Authentication:Google:ClientId"];
                     google.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                 });
             //.AddIdentityServerJwt();
             //services.AddAuthentication().AddGoogle(googleOptions =>
             //    {
             //        googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
             //        googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
             //    });
    
             services.Configure<IdentityOptions>(options =>
             {
                 // Default Password settings.
                 options.Password.RequireDigit = false;
                 options.Password.RequireLowercase = false;
                 options.Password.RequireNonAlphanumeric = false;
                 options.Password.RequireUppercase = false;
                 options.Password.RequiredLength = 4;
                 options.Password.RequiredUniqueChars = 1;
             });
    
             services.Configure<ForwardedHeadersOptions>(options =>
             {
                 options.ForwardedHeaders =
                     ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
             });
    
    
             services.ConfigureApplicationCookie(options =>
             {
                 options.AccessDeniedPath = "/Identity/Account/AccessDenied";
                 options.Cookie.Name = "StusseServerAccountCookie";
                 options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
                 options.LoginPath = "/Identity/Account/Login";
                 options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
                 options.SlidingExpiration = true;
             });
    
             services.AddControllersWithViews();
             services.AddRazorPages();
         }
    
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
         {
             app.UseForwardedHeaders();
    
             if (env.IsDevelopment())
             {
                 app.UseDeveloperExceptionPage();
                 app.UseDatabaseErrorPage();
                 app.UseWebAssemblyDebugging();
             }
             else
             {
                 app.UseExceptionHandler("/Error");
                 app.UseHsts();
             }
    
             app.UseHttpsRedirection();
             app.UseBlazorFrameworkFiles();
             app.UseStaticFiles();
    
             app.UseRouting();
    
             app.UseIdentityServer();
             app.UseAuthentication();
             app.UseAuthorization();
             app.UseCookiePolicy();
    
             app.UseCors();
    
             app.UseEndpoints(endpoints =>
             {
                 endpoints.MapRazorPages();
                 endpoints.MapControllers();
                 endpoints.MapFallbackToFile("index.html");
             });
         }
     }







windows-server-iisdotnet-aspnet-core-generaldotnet-aspnet-core-blazordotnet-entity-framework-core
grafik.png (6.8 KiB)
grafik.png (17.3 KiB)
grafik.png (18.3 KiB)
· 21
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I'm not sure what steps reproduce this issue. I'm assuming you are using the IdentityServer4 template. This template creates three projects; client, server, and shared. You must login from the client application not the token server. If you open the token server and login then you only have an auth cookie for the token server not the client. The client application does not know you logged in.

0 Votes 0 ·

So how do i let the Client know that the User is logged in?
I Thought I'm Adding the Token to the User when add in Client Program.cs
104932-grafik.png


You can Recreate the Scenario:
Register a Account - Confirm it (You can use any E-Mail you want and Password (Password Rules)
after Registration you get Redirected Back to the Blazor Main Site and the Authentication starts to Fail
Try to Open Weatherforecast and Login.

It works Fine Local, but on my Live Server it doesn't
.So why the Client knows Local in Debug Mode that I'm Logged in, but in the Live Environment the Client doesn't know it anymore?




Edit:
My Main Question is, why is the Client in the Live Environment loosing the Token and in the Local Environment not?

0 Votes 0 ·
grafik.png (18.9 KiB)

I still do not understand the steps to reproduce the issue. First, an account is created from the Identity Server application. From the main application select the weather forecast API. The browser is redirected to the authentication server to login. After a successful login the browser is redirected back to the main app. The main app does an HttpClient request to the Web API app. Is this where the authentication fails? You are not able to call Web API action?

0 Votes 0 ·

Still simple:
the Registration as well Login is working. And in the Backend (Server Side WASM) you stay logged in once you are logged in.
Once you are Logged in you get Routed back to the Main Website (Client Side of the WASM) and then you get kicked out.
You cant access Weather Forecast because the Logins fails, You Cant Log Out, the Navigation Menu not change because the Token seems to be missing. If you Press Login again you get routed to the (WASM Server Side) you get Logged in automatic and redirected to the website and there the login fails again.

You can Easy Reproduce the Steps if you go to the Website which I Posted already in my first Entry and start the Process.

0 Votes 0 ·

You can Easy Reproduce the Steps if you go to the Website which I Posted already in my first Entry and start the Process.

Your site is protected by a certificate. I cannot access the site. Also, are you providing the link to the identity server or the client app? We need the client app.

Anyway, I cannot reproduce this issue.



0 Votes 0 ·

I Appreciate your Effort, but since you not even can accept a Certificate that comes from an Official License Provider, I'm sure you cant help me.

Also please stop Posting always a new Answer, you asking Questions and not posting Answers, you can Comment on the main Post to ask questions and keep this Post Clean, Thanks.

0 Votes 0 ·
Show more comments

Use the Brower's dev tools (F12) and view the network tab. On my end, the token endpoint throws a 400 error rather than returning an access token which means the server does not like the client request. Enabling logging in the web.config on the server and set the environment to development. Hopefully, you'll be able to log the error.


0 Votes 0 ·

Had no time to continue so far.

The Dev Tools weren't really helpful so far, since they only say "Authentication Failed" and the /connect/token Bad Request.

I still not understand why they are happening but I'm reading myself more into the Security Structure at the moment to understand more properly what's going on.

Btw, the Access of the Website should now work correctly, I was missing to Rewrite the http URLs to https after the reinstallation of the Server. Would be cool if you can check that out if the problem remains for you or if it's solved.

Thanks for your Help so far.

0 Votes 0 ·

I suggest you could also use fiddler to capture the different request which send from the successfully login request and the failed login connection. Then you could compare these different request. After found out the difference, then you could check the IIS setting to make sure there is any setting which will cause this difference.

0 Votes 0 ·

0 Answers