ServiceStack - prevent unauthorized access to static files - authentication

I understand there is more than one way of handling service authentication/authorization, but I cannot make it work for static files.
Is there a way of configuring the behavior to be the same as with services; if not authenticated a request to index.html should redirect to login page the same as a request to secured dto/service.
I am currently looking into RawHttpHandlers but since it is too early in the pipeline how do I get the authentication setup in the apphost config?
thanks in advance
Gjergji

You would have to use IAppHost.RawHttpHandlers because that's the only custom handler in ServiceStack's Request Pipeline that gets executed before the built-in static file handling is accessed.
But you should still be able to access the Users Session with the available extension methods, e.g:
this.RawHttpHandlers.Add(httpReq =>
{
var isStaticFileRequest = httpReq.PathInfo.StartsWith("/static");
if (isStaticFileRequest)
{
var session = httpReq.GetSession();
if (!session.HasRole("TheRole"))
return new ForbiddenHttpHandler();
}
return null;
});
This handler simply checks if it's a request for a static file, in this case the path info starts with /static, and if is checks the user session if they have the required role, if not it returns a Forbidden request, otherwise it returns null to tell ServiceStack to continue executing the request.
Note: if it's needed you can access any registered dependency from outside of ServiceStack with HostContext.Resolve, e.g:
var authRepo = HostContext.Resolve<IAuthRepository>();

Related

Negotiate with Cookie Fallback in ASP.NET Core

I have a setup as described in the title consisting of a default policy containing a requirement that can be fulfilled through a respective handler:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authorizationContext, AccessRequirement requirement)
{
var httpContext = _httpContextAccessor.HttpContext;
var identity = authorizationContext.User.Identity;
if (!identity.IsAuthenticated)
{
var result = await httpContext.AuthenticateAsync({"Negotiate || Cookie"});
if (!result.Succeeded)
{
return;
}
}
authorizationContext.Succeed(requirement);
}
As soon as I do not invoke these handlers through the default policy, the site will be displaying an simple 401 even if Negotiate authentication could be resolved. A way to solve this without handlers might actually solve the subsequent issue as well.
If i prioritize Negotiate (through explicitly setting default scheme or by registering it first) the user will be prompted for his Active Directory credentials if Negotiate is unavailable, while it should instead fall back to Cookie authentication.
However, if I prioritize Cookie authentication, the user will be prompted for his Cookie dedicated credentials even if Negotiate could work.
Is there any configuration that allows for some sort of try-catch mechanism to attempt Negotiate and immediately fall back to another scheme if it is unresolvable?
I tampered by setting different DefaultChallengeScheme parameters but was unsuccessful this far.
For the problem of 401: when u add service in startup:services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme). Here "JwtBearerDefaults.AuthenticationScheme" is the name of the scheme to use by default when a specific scheme isn't requested. However, when an authentication challenge is invoked by Authorization, a JWT bearer scheme returning a 401 result with a www-authenticate: bearer header.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0
If u wanna use multiple authentication methods u can try "AuthenticationSchemeOptions Class", inside the class has properties can be set.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationschemeoptions?view=aspnetcore-5.0

How do you configure ASP.Net TestHost to work with OpenId Connect?

I have an ASP.Net Core application configured to issue and authenticate JWT bearer tokens. Clients are able to successfully retrieve bearer tokens and authenticate with the token when the site is hosted in Kestrel.
I also have a suite of integration tests which use Microsoft.AspNetCore.TestHost.TestServer. Prior to adding authentication, the tests were able to successfully make requests against the application. After adding authentication, I started getting errors pertaining to accessing open id configuration. The specific exception I'm seeing is this:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware[3]
Exception occurred while processing message.
System.InvalidOperationException: IDX10803: Unable to obtain configuration from: 'http://localhost/.well-known/openid-configuration'. ---> System.IO.IOException: IDX10804: Unable to retrieve document from: 'http://localhost/.well-known/openid-configuration'. ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
Based on my research, this is sometimes triggered when the Authority is set to a different host than the hosting server. For instance, Kestrel runs at http://localhost:5000 by default which is what I had my Authority set to initially, but upon setting it to what the TestServer is emulating (http://localhost), it still gives the same error. Here is my authentication configuration:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateAudience = true
},
Audience = "Anything",
Authority = "http://localhost"
});
What is odd is that attempting to hit the URL directly from the Integration test works fine:
So how do you configure the ASP.Net TestServer and OpenId Connect infrastructure to work together?
=== EDIT ====
In reflecting on this a bit, it occurred to me that the issue is that the JWT authorization internals is trying to make a request to http://localhost port 80, but it isn't trying to make the request using the TestServer and is therefore looking for a real server. Since there isn't one, it's never going to authenticate. It looks like the next step is to see if there is some way to turn off the Authority check or extend the infrastructure in some way to allow for it to use the TestServer as the host.
The JWT infrastructure does indeed try to make an HTTP request by default. I was able to get it working by setting the JwtBearerOptions.ConfigurationManager property to a new instance of OpenIdConnectionConfigurationRetriever() which supplied an IDocumentResolver provided by DI:
ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
authority + "/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
_documentRetriever),
In the production code, I just register the default with my container (Autofac):
builder.RegisterType<HttpDocumentRetriever>().As<IDocumentRetriever>();
I was already using a derived Setup class for my integration tests which follows the Template Method pattern to configure the container, so I was able to override the IDocumentRetriever instance with one that returns the results from the TestServer instance.
I did run into an additional snag which is that the TestServer's client seemed to hang when a request was made (the one initiated from JWT calling my IDocumentRetriever) while another request was already outstanding (the one that initiated the request to begin with), so I had to make the request beforehand and supply the cached results from my IDocumentRetriever:
public class TestServerDocumentRetriever : IDocumentRetriever
{
readonly IOpenIdConfigurationAccessor _openIdConfigurationAccessor;
public TestServerDocumentRetriever(IOpenIdConfigurationAccessor openIdConfigurationAccessor)
{
_openIdConfigurationAccessor = openIdConfigurationAccessor;
}
public Task<string> GetDocumentAsync(string address, CancellationToken cancel)
{
return Task.FromResult(_openIdConfigurationAccessor.GetOpenIdConfiguration());
}
}

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?

Simple custom authenticator in JAX-RS

In JAX-RS (or Jersey) REST service, I'm trying to make custom authentication for my users in database.
Currently I have #GET annotated method which should interact with user asking for credentials just like it is done in Spring framework with authentication provider - no custom login form, just plain HTTP login with browser popup form.
Currently I can handle HTTP Basic Access Authentication provided in header, but I need to ask for credentials before accessing content interactively and then make token-based authentication on this base.
I have to keep the application light-weight but I don't know how can this "easy" task be done..
Edit: I found something in Wildfly configuration (I'm using 9 Final version) but I don't know how to use it for login using datasource..
If you already can handle HTTP Basic authentication, then you only need to get a a "login form" from the browser? We solved this by implementing an javax.ws.rs.ext.ExceptionMapper and overriding toResponse(Throwable ex). Our app throws a NotAuthenticatedException which gets mapped to javax.ws.rs.core.Response.Status.UNAUTHORIZED. Then we add a response header appropriately:
#Provider
public class RESTExMapper implements ExceptionMapper<Throwable>
{
#Override
public Response toResponse(Throwable ex)
{
//our application maps a not logged in exception to javax.ws.rs.core.Response.Status.UNAUTHORIZED in this Pair
Pair<Integer, ObjectMap> ret = buildResponse( unwrap( ex));
ResponseBuilder rb = Response.status( ret.left()).entity( ret.right()).type( "application/json");
if( ret.left() == UNAUTHORIZED.getStatusCode())
return rb.header( HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"YOUR SERVICE NAME\"").build();
else
return rb.build();
}
The important part is setting the response header if the user is not logged in, that makes the browser display the HTTP Basic Login Dialog.

NetworkCredentials and Authorization in WebApi

I am having a few problems trying to connect to a ASP.NET webapi service (which I am running myself) from a sample console app using WebClient. The webapi is the typical sample site from MVC4:
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new string[] { "value1", "value2" });
}
The Controller is decorated with a custom Authenticate attribute:
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add("WWW-Authenticate", "Basic realm=\"localhost\"");
actionContext.Response = response;
return;
}
}
The client code is the usual:
var wb = WebRequest.Create("http://localhost:64921/Values");
wb.Credentials = new NetworkCredential("xxx", "xxx");
var aaa = wb.GetResponse();
Console.WriteLine(aaa);
Console.ReadLine();
Now, I know that the WebClient or WebRequest are supposed to wait for a 401 before sending credentials and that is exactly what I am trying to do here.
Needless to say with the setup above nothing works. I have gone into the IIS express config and changed the following:
<basicAuthentication enabled="true" /> (in the security section)
<add name="BasicAuthenticationModule" lockItem="false" /> (in the modules section)
The problem that I am having is that the 401 gets returned even before the server code is actualy hit. I mean that if I stick a breakpoint into the Controller or the Attribute they are not hit. The details of the error are the usual long text about error 401.2 which I reckon is something to do with IIS configs, but using IIS express and not the nice IIS I do not have a nice GUI to fix this. Can anyone help?
Thanks a lot!
In the IIS config, you have enabled Basic auth processing, so IIS returns the 401 if there are no credentials or the credentials are invalid.
If you want your code to do the basic auth processing, then you need to tell IIS to allow anonymous access.
EDIT from comments
If you ask IIS to do basic auth it will check credentials against Windows accounts. This will act before the server code runs, so the Custom Auth Filter will not be hit. In this case the headers returned will be correct and you will see the WebClient performing the double request (one anonymous, one with credentials). If the WebClient does not use a computer or domain account (with read permissions on the folder where the site is located), the request will fail.
If you want to do authentication/authorization yourself, you need to tell IIS express not to do any auth and then do it all yourself... this basically means leaving everything as it is in the config (in your case reverting the pieces of config shown in the question) and sending the correct headers, which you already do. If you debug, you will see the Authenticate filter being hit twice, the first time it will be an anonymous that will go inside the if and generate your HTTP 401 Challenge response, the second time it will have credentials in the form of a standard Basic Authorization header: Basic <BASE64_ENCODED_CREDENTIALS>