Blazor (server side) authentication and static files - asp.net-core

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).

Related

Kerberos and WindowsIdentity impersonation in ASP.NET Core

I have an ASP.NET Core 5 web app (actually a blazor server app) and another ASP.NET Core 5 Web API, both running on IIS in a domain environment.
I have configured Windows auth so that users can authenticate with the Blazor server app, and this returns the expected domain user's identity from the HttpContext.
If I use the Web API through Swagger to get the users identity from the HttpContext, I also get the expected domain user's identity. However if naively call from the Blazor server app using the HttpClient (given by DI) to the Web API, I get the app pool identity for the user instead.
There are hundreds of posts on this subject and why this happens (i.e.)
https://github.com/dotnet/runtime/issues/17828
.Net Core WindowsIdentity impersonation does not seem to be working
However my question is essentially even if I do manage to get the stars to align and get all the infrastructure config correct is there any way to get the middleware to provide an HttpClient that is already "impersonated" or am I forced to wrap every use of HttpClient something like this:
https://stackoverflow.com/a/66511109/29411
IPrincipal p = _httpContextAccessor.HttpContext.User;
HttpResponseMessage result = null;
if (p.Identity is WindowsIdentity wid)
{
await WindowsIdentity.RunImpersonated(wid.AccessToken, async () =>
{
result = await _client.GetAsync("APIController/Action");
});
}
.Net 5 running Impersonated
(I am running this in Blazor Server)
I have only recently solved this so at present I think everything will need to be wrapped but I'll update if I find anything that solves that.
I have seen a lot of references to using an IHttpConextAccessor and lots of problems with this being null. This article from Microsoft suggests that this shouldn’t be used (Certainly in Blazor)
MS-Docs
Solution:
To get the user to impersonate use the AuthenticationStateProvider and get the user from this and cast to a WindowsIDentity to retrieve the AccessToken.
This works in both a service and a razor component.
Inject the AuthenticationStateProvider and then in your method use the following code:
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
var userToImpersonate = (WindowsIdentity)user.Identity;
await WindowsIdentity.RunImpersonatedAsync(userToImpersonate.AccessToken, async () =>
{
// Your Code in here
}

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

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?

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 Identity redirect to full URL after login

I have a Blazor website (IdentityApp) that includes scaffolded ASP.NET identity integration. That website is processing logins and setting a shared cookie that my other localhost Blazor application (LogicApp) can see.
When I access LogicApp (https://localhost:9876), I check the context for a recognised user (using the shared cookie) and then manually redirect anonymous users to the scaffolded IdentityApp login page: https://localhost:1234/identity/account/login.
I also pass through the fully encoded URL of LogicApp home page in the returnUrl, for example; https://localhost:1234/identity/account/login?returnUrl=https%3A%2F%2Flocalhost%3A9876%2F
The login process is working OK (the shared cookie is set correctly), but then I get an exception;
InvalidOperationException: The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.
Within ASP.NET Identity (.NET Core 3.1);
How can I configure IdentityApp to allow full urls within returnUrl?
or,
How can I override the scaffolded Login logic to take control of the redirect and use my returnUrl value?
So I found an answer to my own problem (but still interested to hear of alternative solutions)..
Within the scaffolded pages locate Login.cshtml.cs and replace the reference to LocalRedirect(...) in in the OnPostAsync(string returnUrl = null) method, for example;
...
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
//return LocalRedirect(returnUrl); // remove this.
return Redirect(returnUrl); //TODO: define a list of acceptable return Urls (localhost / yourdomain.com etc)
}
...
You may also need to do this in Login2FA.cshtml.cs as well as other places that deal with this ReturnUrl value.
NOTE: LocalRedirect is used to prevent malicious redirects occurring so it would be wise to also add some logic to check the returnUrls that have been passed in.

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?