What is the correct way to host .well-known/openid-configuration? - asp.net-core

I have a Blazor Server Project based on ASP.NET Core 5. I want to host my own openid-configuration discovery file. Since this file is served while running the OIDC workflow I want to verify what is the correct way to host this file. So far I have tried the following and only option 2 works.
using wwwroot/.well-known
This involves hosting the openid-configuration file statically in the wwwroot folder of my blazor server project.
After this if I run he project and try to access the file using localhost:44382/.well-known/openid-configuration, the file is not served.
Using Controllers
For this I just added a simple controller to my blazor project and specified .well-known/openid-configuration as a route for my anonymous controller HTTPGET action.
public class OidcConfigurationController : Controller
{
[HttpGet(".well-known/openid-configuration")]
public JsonResult OpenIdConfiguration()
{
return Json(new Storage.Storables.Security.OIDC.Configuration());
}
}
Now if I run the project with Option 2 and try to reach the localhost:44382/.well-known/openid-configuration the configuration JSON is served correctly.
Is option 2 the correct way to serve the OpenId-Configuration using ASP.NET Core and Blazor server project ? Will it cause any issues if I publish the server (for e.g. to Azure)

The reason why your first method is not working is that you don't serve a static file in a way the static file extensions assume you do. You missing a file ending, otherwise, the request isn't recognized as a file.
That said, you can write your own middleware. Give the file a proper ending like .json. If the resources /.well-known/openid-configuration/ is requested, you change the requested path to /.well-known/openid-configuration.json and let the static file extension handle the rest.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.Use(async (context, next) =>
{
if (context.Request.Path == "/.well-known/openid-configuration")
{
context.Request.Path = "/.well-known/openid-configuration.json";
}
await next();
});
app.UseStaticFiles();
...
}
For more information about writing a middleware have a look at the documentation https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write
However, you ran into the problem - I guess - because mostly this document is generated on the fly based on the configuration of your open id connect server like IdentityServer. So, maybe there is away around the static file?

Related

Blazor (server side) authentication and static files

As per Microsoft's recommendation, I am using a custom AuthenticationStateProvider service for handling authentication/authorization for a Blazor server page.
It all works fine within razor components, where I can use the [Authorize] attribute or the AuthorizeView/Authorized/NotAuthorized tags.
Now, I wanted to serve static files outside the wwwroot folder but have control if the user is authenticated or not in order to serve the files.
Is there a way to control access to static files served outside the wwwroot folder?
What I found is something similar to (in program or startup):
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = (context) =>
{
if (context.Context.Request.Path.StartsWithSegments("/MyRequestPath"))
{
context.Context.Response.Headers.Add("Cache-Control", "no-store");
if (!context.Context.User.Identity.IsAuthenticated)
{
context.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Context.Response.ContentLength = 0;
context.Context.Response.Body = Stream.Null;
}
}
},
FileProvider = new PhysicalFileProvider("PathToMyFilesOutsidewwwroot"),
RequestPath = "/RequestPath"
});
The problem with that is that is uses Context.User.Identity.IsAuthenticated, i.e., it uses HTTPContext, which is not available within a Blazor page (and that is why we have to use AuthenticationStateProvider).
I'd like to stick to just using Blazor best practices, and not try to circumvent it via scaffoldding, javascript, or whatever.
Thanks in advance.
In the end I sticked to using the app.UseStaticFiles... approach, but in order to make it work I had to add authentication via cookies (outside of Blazor). Inside of Blazor I still use AuthenticationStateProvider, so the only thing that I had to take care is to authenticate via cookies and AuthenticationStateProvider at the same time when a user logs in. I suppose it makes sense, because authentication via cookies (prior to entering the Blazor "environment") gives me also the chance to call controllers or other pages out the razor components while still being authenticated (via cookies).

Role based Access to Static Content in ASP.NET CORE

Is there any way I can give access to static content based on their role. In my application, the authentication is done through Azure Active AD and the contents should be accessed based on their role e.g. employee should access all employee pages and students should access student pages. This is how my solution explorer looks like.
Solution Explorer
I know this is duplicate of Secure requests to .html files in ASP.NET Core but I couldn't find any approach to implement the solution. I have made new folder intranet outside the wwwRoot to serve my static content but still need to know how can I authorize the user and and serve role based static files.
As the document said, you could store the static files outside of wwwroot and any directory accessible to the Static File Middleware (for example: MyStaticFiles folder, like this), then, you could serve them via an action method to which authorization is applied and return a FileResult object:
[Authorize(Roles = "User")]
public IActionResult BannerImage()
{
var filePath = Path.Combine(
_env.ContentRootPath, "MyStaticFiles", "images", "Image1.jpg");
return PhysicalFile(filePath, "image/jpeg");
}
Then, you could view image by click the following link:
<a asp-action="BannerImage" asp-controller="Home">View Image</a>
[Note] After using the above method, if the authorize not working, try to clear the cache, perhaps the issue is related to the browser cache. Besides, if you meet the "HTTP Error 404.15 - Not Found" error, try to add [AllowAnonymous] attribute for other controller action method.

ASP.NET Core (Blazor) Loading customer specific configuration on startup by using Url or Virtual Path

Our application (Server-side Blazor in .NET Core 3.1) runs within IIS on a Windows Server. We have multiple sites in IIS running the same application but with different URL's for different customers.
At startup in (ConfigureServices) we want to load customer configuration for the application from a config file. That way we can have multiple instances of the application running with different configs. Loading this information from the database is not an option because the config contains the details to connect to the database.
In ASP.NET Framework we would have access to the virtual path or (sub)domain name in the Global and then load the configuration based on that information.
We need the same access in our ASP.Net Core applications or another work around.
Is there any to achieve the same result?
A better way to distinguish sites is by URL. The domain name and port bound to each site in IIS will not be repeated.
You can refer to these code to get URL in startup.cs.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IHostApplicationLifetime lifetime,IServiceProvider serviceProvider)
{
...other code...
lifetime.ApplicationStarted.Register(
() => logAddresses(app.ServerFeatures));
}
static void logAddresses(IFeatureCollection features)
{
var addressFeature = features.Get<IServerAddressesFeature>();
if (addressFeature != null)
{
foreach(var address in addressFeature.Addresses)
{
Console.Out.WriteLine(address);
}
}
}
I was able to grab the Application Pool ID from the Environment and then load the config section as the application config:
var appPool = Environment.GetEnvironmentVariable( "APP_POOL_ID", EnvironmentVariableTarget.Process )
van sectionname = SomeMagicToParseAppPool( appPool );
var config = Configuration.GetSection( sectionName );
if ( config == null ) throw new ApplicationException($" Cannot find config section for {host}");
services.Configure<ApplicationSettings>( config );
This allows me to load a different config for a different site. The only downside is that each application requires their own Application Pool. But that was already a requirement due to a .NET Core ASP.NET app having to run unmanaged in the Application Pool.

Asp.Net Core signalR is not working if publishing inside virtual directory

I have developed an application in asp.net core and used signalR. When i publish it in root directory it works fine, but if i publish it inside virtual directory it doesn't work. My signalR hub is always pointing to the root directory.
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ... other middleware ...
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chat");
});
}
And i have initialized it in client side like this,
chat.js
var connection = new signalR.HubConnection("/chat");
After publishing it inside virtual directory(/development/chatapp/source) it points like this,
http://localhost:100/chat?userId=1
But actually it has to point like this, so that it will work
http://localhost:100/development/chatapp/source/chat?userId=1
I have used asp.net core 2.0 and this signalR version(1.0.0-preview1-final).
Someone please suggest me to resolve this issue.
With the help of my friend i have found the root cause for this issue and i fixed it. Actually javascript doesn't know whether the application is hosted in root folder or sub folder(in virtual directory). It always points the root folder.
So when we are initializing it in js like this(new signalR.HubConnection("/chat")), it points the root directory as below,
http://localhost:100/chat?userId=1
Since javascript doesn't aware of IIS hosting, we need to tell the relative path from c#. So i get my application's base path as follows in controller,
ViewData["PathBase"] = Request.PathBase.ToString();
And if it has some value i just prepend it to '/chat', otherwise i just initialize it as '/chat'.
var connection = new signalR.HubConnection("/development/chatapp/source/chat");
This solves my problem :-)
Kailash P : With the help of my friend i have found the root ...
OK, thanks for your sharing.
I'm publish the SignalR Chat sample to IIS but NOT WORKS --X
SignalRChat :
 Tutorial: Get started with ASP.NET Core SignalR
 https://learn.microsoft.com/zh-tw/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&tabs=visual-studio
*With Visual Studio debugging, the sample are works but NOT WORKS publish to IIS.
In page load, the send message button not enabled because there have some errors :
Failed to load resource: the server responded with a status of 500 (Internal Server Error)  chatHub/negotiate:1
[2019-11-18T06:40:26.977Z] Error: Failed to complete negotiation with the server: Error: Internal Server Error  Utils.ts:179
--
After add the vitural directory the SignalR Chat sample WORKS :
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
->
var connection = new signalR.HubConnectionBuilder().withUrl("/chat1/chatHub").build();
Don't know why there have no any official note about this problem (SignalR publishing).
[NET Core 2.2]
[Visual Studio 2019]

FileServerMiddleware with credentials?

We have the requirement in our enterprise environment to serve static file content from a network share in our ASP.NET Core application. Basically, it gets served under some sub path /content. For this, we have the following code, which works fine:
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider("//our/network/share"),
RequestPath = new PathString("/content"),
EnableDirectoryBrowsing = false
});
Now in production the system user under whose context the web application is hosted has no access to the file share. Thus, we have to use a certain technical domain user to access the files and for this we have to provide credentials (username/password) of this system user to the file server.
Unfortunately, we did not find an option to provide credentials to UseFileServer(). Is it anyway possible?
According to the documentation for UseFileServer it combines the functionality of among other things UseStaticFiles. According to the middleware documentation, the static file module provides no auth checks. They do give you some options on how to accomplish file serving with authorization (again from the middleware docs):
If you want to serve files based on authorization:
Store them outside of wwwroot and any directory accessible to the static file middleware.
Deliver them through a controller action, returning a FileResult where authorization is applied.
Not sure how you are going to pass the username/password to the server. If you plan to use something like basic authentication (and don't want to use the methods outlined above), you can probably modify the headers (when serving the static files) to accomplish the desired effect, but that is a workaround and probably not a good idea.
I would use middleware to protect contents. I will try to write simple example(I assumed you are using any authentication middleware to authenticate your users and my example is for static files).
-- Below code is untested and is just for an illustration--
First, you need to create a middleware something like this:
public class ProtectFileMiddleware
{
private readonly RequestDelegate _next;
public ProtectFileMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/protected"))
{
if (!context.User.IsInRole("Admin"))
{
await context.Authentication.ChallengeAsync();
return;
}
}
await _next(context);
}
}
and use this middleware like below:
public void Configure(IApplicationBuilder app)
{
app.Use(?)Authentication();// it depends on your design
app.UseMiddleware<ProtectFileMiddleware>();
app.UseStaticFiles();
// other
}
Result: if you try to access /protected url as an admin user, you will get expected response otherwise you will take a 401/403 response.
For more flexible way take a look at http://odetocode.com/blogs/scott/archive/2015/10/06/authorization-policies-and-middleware-in-asp-net-5.aspx
Yeah, those answers assume you're asking about client credentials. What you really need is a IFileProvider implementation that has credentials to access a specific resource. I don't know that .NET is very good at accessing files as different users, it usually relies on impersonation. See How to present credentials in order to open file?