Handling Server side routing errors with built in client side routing .NET 5 - asp.net-core

I have a question regarding the cleanest/easiest way to fix a routing issue. To start off, I'm using the .NET 5 framework, and have the following setup in my Startup.cs:
public void ConfigureServices(IServiceCollection services) {
...
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "UI"; });
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
app.UseRouting();
app.UseSpaStaticFiles();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.UseSpa(spa =>
{
if (!env.IsDevelopment()) return;
spa.Options.SourcePath = "UI/";
spa.UseVueCli();
});
...
}
Now, my problem seems to be an issue with the server side routing behavior, however I will let you diagnose. Basically, I am returning a Redirect method that redirects to a CLIENT SIDE route, (the route does NOT exist on the server side), and so when my client receives the response, it always receives the response as a 404 error message. However, if I click the request in the network tab in my browser debugger, it ends up redirecting to the client endpoint properly. I was wondering how I could resolve this 404 error and smoothly redirect to that client endpoint.
Thanks

Related

Authorize attribute using WsFederation never executes annotated controller action

I am using WsFederation middleware to access an ADFS server for authentication. ADFS is given a specific endpoint to call back at the end of the conversation between the middleware and ADFS. If I don't provide an actual endpoint in my code (some action that responds to the route = callback endpoint), I get a 404. If I do implement an action at that endpoint, I get nothing useful (e.g. 'User' not set) and - whatever my action does at the end with respect to a response goes straight back to the user's browser. At no point was the action I decorated with [Authorize] executed.
From startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// set up ADFS authentication
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
}).AddWsFederation(options =>
{
options.MetadataAddress = "<adfs-server>/FederationMetadata/2007-06/FederationMetadata.xml";
options.Wtrealm = "<my-apps-server>/authviaadfs/auth-callback";
}).AddCookie("Cookies", o => { });
// set up custom authorization
services.AddAuthorization(options => { });
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
//app.UseHsts();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
From MyController:
[Authorize]
public IActionResult MyProtectedPage()
{
... code that never, ever, executes when decorated with [Authorize]
}
[Route("/authviaadfs/auth-callback")]
public IActionResult AuthCallback()
{
... code that executes after I log in via ADFS
... response that returns to the original caller of "MyProtectedPage"
}
Can anyone tell me what I'm doing wrong? I've followed the recipe from half-dozen different Googled websites that say "this is how you authenticate to ADFS" (all slightly different, but the gist is the same, including setting only the options for 'MetadataAddress' and 'WtRealm').
Okay, I figured it out - another example of "Googled examples that are useless in the real world" and "bad/missing documentation of how to do something".
There is another "option" to set in addition to "MetadataAddress" and "Wtrealm" if you want to use an endpoint other than "/" - an option called "CallbackPath". This tells the middleware what route in your code to "swallow and process"; if you don't set it (as I didn't originally, following the recipes) then your middleware doesn't know which request to intercept. So as far as the sample code I provided in the question, after setting options.MetadataAddress and options.Wtrealm you would set the following:
options.CallbackPath = "/authviaadfs/auth-callback";
That tells the middleware to, inside the request pipeline, to intercept any calls to "/authviaadfs/auth-callback" and use the request and headers to finish the authentication and then, effectively, give control to your protected controller action. The middleware creates a correlation cookie that it sends along with the first redirect to your ADFS server, then uses that cookie in the "/authviaadfs/auth-callback" to match the return call from ADFS to the proper request context that was being authenticated.

How to set the redirect URI when using Microsoft sign-in in a .NET 5 application?

I have created a .NET 5 application with Microsoft sign-in based on this explanation.
It is working fine when running locally. However, something is going wrong when running the application in Amazon EKS. This became clear to me after reading error message I saw in the browser and after reading the network traffic.
This is how this looks like.
What becomes clear is that there is something wrong with "redirect_uri" (containing http instead of https). This is really frustrating as my application is using https. I use https when opening the application in my browser. It is important to mention that this does not occur when running the application locally on my laptop. What I hope for is that there is a simple way to set the "redirect_uri" property that is used in my code. In this way, I can guarantee that the right redirect uri is used.
Here is the source code I would like to change:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var configSettings = new ConfigSettings();
Configuration.Bind("ConfigSettings", configSettings);
services.AddSingleton(configSettings);
services.AddSingleton<IAuthResponseFactory, AuthResponseFactory>();
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddHealthChecks();
services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(2);
options.Predicate = (check) => check.Tags.Contains("ready");
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health/ready", new HealthCheckOptions()
{
Predicate = (check) => check.Tags.Contains("ready")
});
endpoints.MapHealthChecks("/health/live", new HealthCheckOptions());
});
}
So how do I change my source in a way that I can set the redirect uri correctly?
Looks like you need to enable header forwarding.
Step 1: configure the ForwardedHeadersOptions
services.Configure<ForwardedHeadersOptions>(options =>
{
options.RequireHeaderSymmetry = false;
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// TODO : it's a bit unsafe to allow all Networks and Proxies...
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
Step 2: UseForwardedHeaders in the public void Configure(IApplicationBuilder app, IHostingEnvironment env) method
app.UseForwardedHeaders();
Step 3: Only use UseHttpsRedirection for production
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Forward http to https (only needed for local development because the Azure Linux App Service already enforces https)
app.UseHttpsRedirection();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
See How to set redirect_uri protocol to HTTPS in Azure Web Apps and .net Core X Forwarded Proto not working

Hosting both GRPC service and REST controller in ASP.NET Core 5.0 application

I have a GRPC service written in ASP.NET Core 5.0 and I wanted to add a classic REST controller to explore its inner status during the development phase.
Unfortunately, I am experiencing some issues with routing.
This is the relevant endpoint section of the Startup class
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapGrpcService<TestProviderService>();
endpoints.MapGet("/", async context =>
{
context.Response.Redirect("/docs/index.html");
});
});
The controller is accessible when hitting /values/ but it can't find the action when trying to explore a specific resource /values/123 or /values/1234/2345. (In my real case, the IDs are GUIDs, not integers, if it makes any difference).
Everything works as expected when I comment out
endpoints.MapGrpcService<TestProviderService>();
I also tried to enable HTTP 1 and HTTP 2 side by side by doing
webBuilder.UseStartup<Startup>()
.ConfigureKestrel(options => options.ConfigureEndpointDefaults(o => o.Protocols = HttpProtocols.Http1AndHttp2));
But it didn't help either.
Thanks for your help!
We started with a Client Side Blazor Solution and added a GRPC interface to it. This might lead to your desired result. BTW we also added swagger to it.
In the CreateHostBuilder (program.cs) we added UseUrls("https://localhost:44326", "https://localhost:6000"). The HTML is served from 44326 and the GRPC from port 6000.
private static IHostBuilder CreateHostBuilder(string[] args, WebApiClient webApiClient) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseKestrel()
.UseUrls("https://localhost:44326", "https://localhost:6000");
}).ConfigureServices((IServiceCollection services) => {
services.AddSingleton(webApiClient);
});
The Startup adds Grpc and RazorPages and so on. In Configure we map the GRPC implementation to port 6000:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ServerUpdatesHub>("/ServerUpdatesHub");
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
endpoints.MapGrpcService<MyGRPCService>().RequireHost($"*:6000");
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}

gettting 401 on Hangfire with LocalRequestsOnlyAuthorizationFilter

We are trying to use hangfire with LocalRequestsOnlyAuthorizationFilter. We have deployed the application to IIS. When trying to access the hangfire dashboard from same machine where IIS is deployed, we are getting 401 on hangfire dashboard URL "/jobs". All we are trying to do here is allow to view the dashboard as long as request is coming from same machine where hangfire is deployed. Below is our config on startup.cs file
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
Authorization = new[] { new LocalRequestsOnlyAuthorizationFilter() }
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHangfireDashboard();
});
backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));
}
We checked both remote and local ip and both are same. Is there anything else we are missing here? Just to make sure application is running or not on IIS, we are added the another page and that page is working fine.
Change your Authorization to AuthorizationFilters,
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
AuthorizationFilters = new[] { new LocalRequestsOnlyAuthorizationFilter() }
});
Also, from this post check to make sure ASP.net is correctly installed and integrated pipeline is used for your application pool
This is also another good resource to go through in validating your IIS configuration

.Net Core Middleware hitting multiple times on page request

I am logging a user's audit to the database on every page click and I thought doing this in the middleware was acceptable (And good?) as it gets fired on every HTTP request. However, when I proceed to a new page, the code in the middleware (userService.AddUser()) is being hit 3 times and I am unsure why.
Here is the code:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IUserService userService)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
// My own code.
app.Use(async (context, next) =>
{
// The database insert
userService.AddUser();
await next.Invoke();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
I am probably missing some knowledge as of why this doesn't work.
Thanks
It's being called multiple times due to images not being found on said page.
In the console of the browser, a third party library cannot find an image which is calling a 404.
sort_both.png:1 Failed to load resource: the server responded with a
status of 404 ()
If this happens to you, I would check the console window in case images and or files are missing.