i'm having a heck of a time trying to resolve an issue with authentication using HttpWebRequest.
So we have a SOA solutation that is being load balanced. Part of the solution is that all requests must be authenticated (using Windows Authentication). The other part of the solution is that the load balancer must have anonymous access to a keep alive page. So we've done the appropraite web.config sections as below
<location path="hello.aspx" allowOverride="false">
<system.web>
<authorization>
<allow users="?" />
</authorization>
</system.web>
</location>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
...
</system.web>
we've correctly setup an httpRequest as below
httpRequest.UseDefaultCredentials = true;
httpRequest.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
so here's the problem. When only integrated authentication is enabled everything works great. However when both anonymous and integrated authentication are enabled (with the web.config defined above) we get an extra header coming back
Cache-Control: private
This is causing our client to barf. We can set the CachePolicy to NoCacheNoStore but that's not ideal because other requests can and should be cached. Setting the clientCache DisableCache has no effect.
Any ideas would be appreciated.
Never did find a solution but anyways, for those of you that are interested here's the workaround
public Foo ExecuteRequest(RequestCachePolicy cachePolicy, ...)
{
return ExecuteRequest(new RequestCachePolicy(RequestCacheLevel.Default), ...);
}
private Foo ExecuteRequest(RequestCachePolicy cachePolicy, ...)
{
...
try
{
...
// Make call using HttpWebRequest
...
}
catch (WebException ex)
{
var webResponse = ex.Response as HttpWebResponse;
if ((ex.Status == WebExceptionStatus.ProtocolError) &&
(null != webResponse) &&
(webResponse.StatusCode == HttpStatusCode.Unauthorized) &&
(cachePolicy.Level != RequestCacheLevel.NoCacheNoStore))
{
return ExecuteRequest(new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore), ...);
}
...
}
...
}
Related
I implemented a dotnet core Api where endpoints are defined based on the Controller attribute Route.
I have for example 2 endpoints
api/controller1 and
api/controller2
I want to configure IIS so a client certificate is ignored for controller1 and required for controller2.
In my Api, I implemented the host this way
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel(o =>
{
o.ConfigureHttpsDefaults(o=>o.ClientCertificateMode=ClientCertificateMode.AllowCertificate);
})
.UseIISIntegration()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Debug);
})
.UseNLog();
and configured services
services.AddSingleton<CertificateValidationService>();
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = true;
});
services.AddAuthentication()
.AddCertificate(x =>
{
x.AllowedCertificateTypes = CertificateTypes.All;
x.ValidateValidityPeriod = true;
x.RevocationMode = X509RevocationMode.NoCheck;
x.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
_logger.Trace("Enters OnCertificateValidated");
var validationService =
context.HttpContext.RequestServices.GetService<CertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate))
{
_logger.Trace("OnCertificateValidated success");
context.Success();
}
else
{
_logger.Trace("OnCertificateValidated fail");
context.Fail("invalid certificate");
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
_logger.Trace("Enters OnAuthenticationFailed");
context.Fail("invalid certificate");
return Task.CompletedTask;
}
};
});
Here is the middleware pipeline configuration in Configure method of Startup.cs
if (env.IsLocal())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (context, next) =>
{
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
if (error != null && error.Error is SecurityTokenExpiredException)
{
_logger.Warn($"No valid token provided. {error.Error.Message}");
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
IpUrl = _globalSettings.IdP.Url,
SpName = _globalSettings.IdP.Name,
Authenticate = context.Request.GetEncodedUrl(),
//State = 401,
Msg = "Token expired"
}));
}
else if (error?.Error != null)
{
_logger.Error($"Unexpected error - {error.Error.Message}");
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
State = 500,
Msg = error.Error.Message
}));
}
else
{
await next();
}
});
});
// 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.UseRouting();
app.UseCors("AllowOrigin");
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger(SwaggerHelper.ConfigureSwagger);
app.UseSwaggerUI(SwaggerHelper.ConfigureSwaggerUi);
app.UseEndpoints(endpoints => endpoints.MapControllers());
I tried to use web.config location but the "path" api/controller2 doesn't not actually exists (it's routed) so it has no effect
I created in the app folder faked api/controller2 folders to setup the SSL requirement on it. Unfortunately, I get a 405 because I lose then the routing and there's nothing behind those folders.
The only way I have yet is to "accept" a certificate at the api application level. But then, my front end, as soon as it queries for the first time my API asks for a certificate when it uses only api/controller1
Is there a way or do I have to build and deploy a specific API to have it protected and the other one for not using client certificate ?
Unfortunatelly this is not possible. Certificate validation happens on TLS level, i.e. before the actual request gets to ASP.NET core, so you cannot distinguish by route. It fails even before you could implement such logic.
We had a similar problem and we had to set up two applications, one with certificate validation and one without. The one with certificate validation than called the other app with "normal" (JWT machine-to-machine in our case) authentication and passed certificate parameters along.
This is official docu that states this:
Can I configure my app to require a certificate only on certain paths?
This isn't possible. Remember the certificate exchange is done at the
start of the HTTPS conversation, it's done by the server before the
first request is received on that connection so it's not possible to
scope based on any request fields.
I have a similar issue.
And I found a solution for iis express.
I think for iis it is solved similarly, I will write about it later (if it's will work).
But about solution (starting terms):
I'am on stage testing from visual studio, and i run net core app under iis express integrated in VS.
For my solution i need to request user certificate when user go to url '/certificate/apply/' (only on this page).
Project name is 'TestCore'
Steps:
In visual studio project folder you need to finde hidden folder .vs and in this folder you need to find folder 'config' and file 'applicationhost.config'
In this file you need to find similar as below section with you project configuration:
<location path="TestCore" inheritInChildApplications="false">
<system.webServer>
<modules>
<remove name="WebMatrixSupportModule" />
</modules>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" stdoutLogEnabled="false" hostingModel="InProcess" startupTimeLimit="3600" requestTimeout="23:00:00" />
<httpCompression>
<dynamicTypes>
<add mimeType="text/event-stream" enabled="false" />
</dynamicTypes>
</httpCompression>
</system.webServer>
clone (copy - paste) this section in file and modify copy (change path and add sequryti section):
<location path="TestCore/certificate/apply" inheritInChildApplications="false">
<system.webServer>
<modules>
<remove name="WebMatrixSupportModule" />
</modules>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" stdoutLogEnabled="false" hostingModel="InProcess" startupTimeLimit="3600" requestTimeout="23:00:00" />
<httpCompression>
<dynamicTypes>
<add mimeType="text/event-stream" enabled="false" />
</dynamicTypes>
</httpCompression>
<security>
<access sslFlags="SslNegotiateCert" />
</security>
</system.webServer>
try to start project (for mee it works fine).
I hope i (or some body else) will find same way for IIS.
For IIS and IISExpress, this is possible by using the location element.
I have successfully used it like this (this is added in the web.config, in the configuration element - see here for an example):
<location path="restapi">
<system.webServer>
<security>
<access sslFlags="Ssl,SslNegotiateCert,SslRequireCert"/>
</security>
</system.webServer>
</location>
This means that any route other than restapi e.g., http://localhost/whatever, the server will not require a client certificate to be part of the request. As soon as you hit http://localhost/restapi/whatever2, a client certificate is required (if you call this from a browser, you will get a pop-up asking you to choose a client certificate to post).
For Kestrel, look at the answer provided by Maxim Zabolotskikh.
I can't upload big files and I'm not sure if it's still about a size limitation or about a timeout.
On the controller endpoint, I tried all the attributes I found (at once)
[HttpPost("[action]")]
[DisableRequestSizeLimit]
[RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, BufferBodyLengthLimit = long.MaxValue)]
[RequestSizeLimit(int.MaxValue)]
public async Task UploadForm()
During 'ConfigureServices' I also setup this:
services.Configure<FormOptions>(options =>
{
options.MemoryBufferThreshold = int.MaxValue;
options.ValueLengthLimit = int.MaxValue;
options.ValueCountLimit = int.MaxValue;
options.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart
});
But I still get 404 errors after uploading a part of the file (30 MB are already too much).
Then I even tried setting up the kestrel with the following code, but like that the app doesn't even start (502)
.UseKestrel((KestrelServerOptions o) =>
{
o.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(120);
o.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(120);
o.Limits.MaxRequestBodySize = null;
})
have a look of this Offcial doc.
Solution:
Change the value of maxAllowedContentLength.
Add these code in Web.config(under site/wwwroot on Kudu):
<configuration>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="<valueInBytes>"/>
</requestFiltering>
</security>
</system.webServer>
</configuration>
This should work without restart.
I'm trying to upload a 100MB film to my ASP.NET Core application.
I've set this attribute on my action:
[RequestSizeLimit(1_000_000_000)]
And I also changed my Web.config file to include:
<security>
<requestFiltering>
<!-- This will handle requests up to 700MB (CD700) -->
<requestLimits maxAllowedContentLength="737280000" />
</requestFiltering>
</security>
In other words, I've told IIS to allow files up to 700MBs and I've also told ASP.NET Core to allow files of near 1GB.
But I still get that error. And I can't find the answer. Any ideas?
P.S: Using these configurations, I could pass the 30MB default size. I can upload files of 50 or 70 Mega Bytes.
I think you just need: [DisableRequestSizeLimit]
below is a solution that worked for me to upload Zip files with additional form data to an API running .Net Core 3
// MultipartBodyLengthLimit was needed for zip files with form data.
// [DisableRequestSizeLimit] works for the KESTREL server, but not IIS server
// for IIS: webconfig... <requestLimits maxAllowedContentLength="102428800" />
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
[DisableRequestSizeLimit]
[Consumes("multipart/form-data")] // for Zip files with form data
[HttpPost("MyCustomRoute")]
public IActionResult UploadZippedFiles([FromForm] MyCustomFormObject formData)
{ }
For me (Asp.net core 3.1) the solution was to add these lines in ConfigureServices method of Startup.cs:
// 200 MB
const int maxRequestLimit = 209715200;
// If using IIS
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = maxRequestLimit;
});
// If using Kestrel
services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = maxRequestLimit;
});
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = maxRequestLimit;
x.MultipartBodyLengthLimit = maxRequestLimit;
x.MultipartHeadersLengthLimit = maxRequestLimit;
});
and editing web.config:
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="209715200" />
</requestFiltering>
</security>
</system.webServer>
NOTE: This is issue I faced when I migrated my application from
asp.net core 2.1 to 3.0
To fix this in asp.net core 3.0 I have changed my program.cs to modify maximum request body size like below.
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureKestrel((context, options) =>
{
options.Limits.MaxRequestBodySize = 737280000;
})
.UseStartup<Startup>();
}
}
}
I mean I have just added ConfigureKestrel part and added an attribute above my action method [RequestSizeLimit(737280000)] like below
[HttpPost]
[RequestSizeLimit(737280000)]
[Route("SomeRoute")]
public async Task<ViewResult> MyActionMethodAsync([FromForm]MyViewModel myViewModel)
{
//Some code
return View();
}
And my application started behaving correctly again without throwing BadHttpRequestException: Request body too large
reference: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.0#kestrel-maximum-request-body-size
I was using web.config to configure this (while our api is hosted in IIS):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="157286400" />
</requestFiltering>
</security>
</system.webServer>
But now we are moving our api to linux containers and using Kestrel. Then I configured it like this:
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = 157286400;
})
.UseStartup<Startup>();
})
157286400 = 150mb;
Azure functions have a hard-coded limit of 100MB: https://github.com/Azure/azure-functions-host/issues/5854
I have created a web api that uses the JWT system using this article here. When calling the API from a REST client it works just fine. However when trying to access it from a browser it gives a CORS error since it doesn't send out the correct response headers.
Startup.cs
app.UseCors(CorsOptions.AllowAll);
Note that on my controllers CORS works just fine, it just breaks for the OAuthAuthorizationServer.
CustomOAuthProvider.cs
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var user = Database.Users.FirstOrDefault(u => u.Email == context.UserName);
if (user == null || !BCrypt.Net.BCrypt.Verify(context.Password, user.Password))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return Task.FromResult<object>(null);
}
var companyId = int.Parse(context.OwinContext.Get<string>("company_id"));
var company = user.Companies.FirstOrDefault(c => c.Id == companyId);
if (company == null)
{
context.SetError("invalid_grant", "You don't belong to that company!");
return Task.FromResult<object>(null);
}
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("uue", user.Email));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{ "audience", company.ServerUrl }
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
However after making the call to obtain the token, I only get back these response headers.
Content-Length:1245
Content-Type:text/html
Date:Wed, 20 Apr 2016 20:34:40 GMT
Server:Microsoft-IIS/8.5
X-Powered-By:ASP.NET
Is there something I'm doing wrong?
Note: I'm assuming you are using the same Startup.cs code defined in the liked tutorial.
Try to move the call to app.UseCors(CorsOptions.AllowAll); at the top of your Configuration method in Startup.cs:
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
HttpConfiguration config = new HttpConfiguration();
// Web API routes
config.MapHttpAttributeRoutes();
ConfigureOAuth(app);
app.UseWebApi(config);
}
In Owin every middleware in the pipeline is executed only if the preceding passes through the invocation. For this reason app.UseCors is executed only after the AuthenticationMiddleware (in your case OAuthAuthorizationServer) and only if it does not stop the flow in the pipeline (e.g. OAuth returns a response).
Moving the Cors middleware declaration before other middlewares ensures you that it is executed for each request.
Make sure you allow CORS in web config
<httpProtocol>
<customHeaders>
<clear />
<add name="Access-Control-Allow-Methods" value="GET,PUT,POST,OPTIONS,DEBUG" />
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="authorization,content-type" />
</customHeaders>
</httpProtocol>
I am setting up facebook authentication with servicestack and have been getting the return type #f=Unknown, I've tracked it down to coming from the authentication block:
try
{
var contents = accessTokenUrl.DownloadUrl();
var authInfo = HttpUtility.ParseQueryString(contents);
tokens.AccessTokenSecret = authInfo["access_token"];
session.IsAuthenticated = true;
authService.SaveSession(session, SessionExpiry);
OnAuthenticated(authService, session, tokens, authInfo.ToDictionary());
//Haz access!
return authService.Redirect(session.ReferrerUrl.AddHashParam("s", "1"));
}
catch (WebException we)
{
var statusCode = ((HttpWebResponse)we.Response).StatusCode;
if (statusCode == HttpStatusCode.BadRequest)
{
return authService.Redirect(session.ReferrerUrl.AddHashParam("f", "AccessTokenFailed"));
}
}
//Shouldn't get here
return authService.Redirect(session.ReferrerUrl.AddHashParam("f", "Unknown"));
The reason for it dropping through is the catch checks the response status code. In my scenario I am receiving 407 Proxy Authentication Required.
I've tracked it down further to the line:
var contents = accessTokenUrl.DownloadUrl();
Can anybody help with how I put in place the required proxy authentication?
For info, my app is running in a windows environment, it is run as an windows authenticated user so has permission access the proxy server, I just need to tell the code to use this - or any - credentials.
Thanks in anticipation
You can try setting the default proxy for .NET applications by setting the <defaultProxy/> in your Web.Config:
<configuration>
<system.net>
<defaultProxy>
<proxy
usesystemdefaults="true"
proxyaddress="http://192.168.1.10:3128"
bypassonlocal="true"
/>
<bypasslist
<add address="[a-z]+\.contoso\.com" />
</bypasslist>
</defaultProxy>
</system.net>
</configuration>