I'm developing w/VS2017 using asp.net core 2.0.1 with IIS express. My problem is I cannot get IIS express to negotiate for a SSL client certificate.
I have tried editing the applicationhost.config located in the .vs\config folder of the project by changing the <access sslFlags="None" /> to <access sslFlags="Ssl, SslNegotiateCert" />
I have also tried requiring a certificate using <access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" /> but this causes IIS express to crash and produce a HttpFailure*.html file which reports: The page you are attempting to access requires your browser to have a Secure Sockets Layer (SSL) client certificate that the Web server recognizes.
My Startup.cs contains services.Configure<IISOptions>(options => {options.ForwardClientCertificate = true; }); and I have called UseIISIntegration in the BuildWebHost method.
My application works as expected when I deploy it on IIS and require SSL client certificates.
@javiercn, do you have any thoughts regarding how we can help @vachris? Thanks!
@blowdart @BillHiebert Do you guys know anything about this?
IIS Express - @shirhatti ?
@VAchris Did you add <iisClientCertificateMappingAuthentication enabled="true"></iisClientCertificateMappingAuthentication>
to your config?
@shirhatti nope I did not add it; I'm not mapping a certificate to one or many user accounts. also it's not needed when I run my app in IIS.
if you'd like me to try that i can. what account would I map it to?
Ahh my bad. I incorrectly assumed you were trying to map a certificate to a user identity. It shouldn't be necessary.
@Tratcher @pan-wang Any ideas what could be going on here?
have a Secure Sockets Layer (SSL) client certificate that the Web server recognizes
Servers can advertise a list of certificate issuers they will accept, and if your cert doesn't match it won't be sent. It sounds like you don't have any valid client certificates in your cert store.
So, I got this to work over IIS Express with Kestrel feeding it in ASP.NET Core 2.x (web api project). Here are the pertinent bits:
.vs/config/applicationHost.config / system.webServer / security / access
<access sslFlags="Ssl, SslNegotiateCert" />
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<HttpsConnectionAdapterOptions>(options =>
{
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
options.CheckCertificateRevocation = false;
options.ClientCertificateValidation = (certificate2, chain, policyErrors) =>
{
// accept any cert (testing purposes only)
return true;
};
});
services.AddMvc();
}
Simple IAuthorizationFilter attribute for putting on controller class
[AttributeUsage(AttributeTargets.Class)]
public class RequireClientCertificateAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
#if DEBUG
// dig out cert from kestrel pipeline
X509Certificate2 certificate = context.HttpContext.Connection.ClientCertificate;
if (certificate == null)
{
// missing cert
context.Result = new UnauthorizedResult();
}
else
{
Trace.WriteLine(certificate.SubjectName.Name, "RequireClientCertificate");
// TODO: authorize
}
#else
// Azure App Service will pass base64 encoded certificate in a header
string header = context.HttpContext.Request.Headers["X-ARR-ClientCert"];
if (String.IsNullOrEmpty(header))
{
// missing cert
context.Result = new UnauthorizedResult();
}
else
{
byte[] data = Convert.FromBase64String(header);
using (var certificate = new X509Certificate2(data)) {
Trace.WriteLine(certificate.SubjectName.Name, "RequireClientCertificate");
}
// TODO: authorize
}
#endif
}
}
Just put that attribute at the head of your controller. My "Release" build is targeted at Azure, so you'll have to change this obviously if you're going for full IIS. Configure IISOptions in ConfigureServices to forward the client certificate, if IIS.
I don't touch the WebHost builder. I suggest using Chrome to test instead of IE because Chrome will always prompt for you to pick a certificate even if there's only one. IE will not prompt for one cert (but will for two or more.)
Huh, interesting that Azure puts it in a weird header.
@blowdart Well, the header name should give it away: It's being passed along from the Application Request Routing layer (ARR). Essentially it's proxied. When you use the OOTB App Service authentication options (google, facebook, live, etc) controlled via the portal itself, you get an identity in a HTTP header too.
Yup. It's a shame we don't map that properly in our header forwarding middleware. I added it to my cert auth handler :) https://github.com/blowdart/idunno.Authentication/commit/ce66248872fcdad02814a373e890f59ff33bd144
Why not just add a middleware that changes the name of the incoming header to match what the rest of the pipeline is expecting?
That's what I would have thought the header fowarding middleware did. But it doesn't. Sadness.
I was suggesting a custom one to 'correct' the current sad middleware. Whatevs :)
@VAchris, it seems this discussions went to slightly different direction. Were you able to fix your issue or not?
@mkArtakMSFT yes I was able to fix my issue and I don't mind the different direction if it fosters improvement of asp.net core :v: :+1:
@VAchris Can you say like you resolve the problem? I have the same.
Thanks.
Most helpful comment
So, I got this to work over IIS Express with Kestrel feeding it in ASP.NET Core 2.x (web api project). Here are the pertinent bits:
.vs/config/applicationHost.config / system.webServer / security / access
Startup.cs
Simple IAuthorizationFilter attribute for putting on controller class
Just put that attribute at the head of your controller. My "Release" build is targeted at Azure, so you'll have to change this obviously if you're going for full IIS. Configure IISOptions in ConfigureServices to forward the client certificate, if IIS.
I don't touch the WebHost builder. I suggest using Chrome to test instead of IE because Chrome will always prompt for you to pick a certificate even if there's only one. IE will not prompt for one cert (but will for two or more.)