.Net Core Web API with Client Certificate Authentication - asp.net

I've developed a simple WEB API service in .Net Core 2.1
I'm trying to implement a client certificate authentication, so I can give access to the APIs only to the clients that have a specific certificate installed on their machine.
The clients access the API using a browser (Chrome, Edge, IE11 or Firefox).
I've added in the API method the request for the certificate:
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
X509Certificate2 cert = Request.HttpContext.Connection.ClientCertificate;
if (cert!=null && cert.Verify())
{
//more verification here...
return Content("....", "application/json");
}
else
{
return Content("....", "application/json");
}
}
then I've installed a self-signed certificate and added to the Trusted Root, enabling the Client Authentication purpose.
but the variable cert is always null and the browser didn't even prompt me to use a certificate when I request the page.
I suppose because I have to set somewhere that the web server must ask for the client certificate as it is possible to set in IIS, but in my development environment, I'm using IIS Express.
How can I force IIS express to request a client certificate?

For proper certificate authentication using the ASP.NET Core authentication stack, you can also check out idunno.Authentication.Certificate by Barry Dorrans himself. It allows you to enable certificate authentication for your application and handles it like any other authentication scheme, so you can keep actual certificate-based logic out of your business logic.
This project sort of contains an implementation of Certificate Authentication for ASP.NET Core. Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal.
You must configure your host for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using.
Make sure to also check out the “documentation” on how to set this up properly, since it requires configuration of the host to work properly, just like you did with IIS Express. Instructions for other servers like raw Kestrel, IIS, Azure or general reverse proxies are included.

In order to enable IIS Express to start requesting client certificates and therefore pass them to the server side, the configuration file must be edited:
The whole configuration is inside the solution folder in the .vs\config\applicationhost.config
Ensure the following values are set:
<security>
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
and
<iisClientCertificateMappingAuthentication enabled="true"></iisClientCertificateMappingAuthentication>

For local testing, you can enable SSL in IIS Express from Visual Studio. In the Properties window, set SSL Enabled to True. Note the value of SSL URL; use this URL for testing HTTPS connections.
For Who needs
Details here

For .NET 3.1+ there is now official package supporting this feature:
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices
.GetRequiredService<ICertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate))
{
var claims = new[]
{
new Claim(
ClaimTypes.NameIdentifier,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(
ClaimTypes.Name,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
return Task.CompletedTask;
}
};
});
There is also configuration required on the server side, see:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-6.0

Related

Automatic Windows Authentication over IIS, ASP.NET 5 and Microsoft SQL Server

There is a lot of relevant Info, I hope I won't forget anything. We want an automatic Windowslogin into an intranet website in our domain.
For testing on a client I'm using a chrome browser that has the website whitelisted.
The Server runs IIS Version 10.0.19041 running a ASP.NET 5 website. It connects to a database MS SQL Server in the same domain.
The Connection String has the parameter Integrated Security=SSPI and works with Apps running locally on clients.
This is what is happening for now: Normally the website would ask for credentials, but since it's in the white list I assume it now uses the Windows credentials, how it's explained here https://stackoverflow.com/a/24875309/15631999
using (SqlConnection Verbindung = new SqlConnection(sqlConnectString))
{
Verbindung.Open();
the "Login failed for user" error is thrown, because it tries to connect with the active webserver user, not with the automatically logged in client. System.Security.Principal.WindowsIdentity.GetCurrent().Name returns the account identity in the AppPool.
What we did: (might not all be relevant ...)
We googled a lot the last couple of days to find out what to do. The general keywords seem to be Windows Authentication, maybe impersonation, NTLM.
So we added the Authentication to the ConfigureServices. Now it looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
services.AddAuthentication(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.Ntlm);
services.AddSession();
services.AddAuthorization(options =>
{
options.AddPolicy("AuthorizationMode", (policy) =>
{
//unauthenticated
policy.RequireAssertion((e) => true);
});
});
//services.AddMvc();
services.AddHttpContextAccessor();
}
We also tried it with IISDefaults.AuthenticationScheme and without Authorization.
Configure Function snippet:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
SessionOptions s = new SessionOptions();
s.IdleTimeout = System.TimeSpan.FromMinutes(30);
s.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
s.Cookie.Name = ".acclaro.de.Session";
s.Cookie.IsEssential = true;
app.UseSession(s);
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
(...)
In IIS/Sites/[Website]/Authentication we disabled Anonymous Authentication and enabled Windows Authentication. This means, I think, I don't have to edit the web.config.
We tried it both with Impersonation enabled and disabled. Basic Authentication seem to always ask for Username and PW. So this is not what we want.
We read that I have to change the order of the Providers. Putting Negotiate under NTLM, or downright deleting it doesn't change anything.
I also read that I have to add SPNs. So I executed setspn -a http/[url] [domain controller computer name] at our domain controller.
I don't know what I'm missing. Maybe I just haven't tried the right combination yet. If anyone can help, that would be appreciated. ( Also I hope impersonating other users won't produce problems, when I write in a local temp folder on the Server. I had some Access denied Exceptions in the past. )
If some relevant info is missing, please tell me.
It's a common misconception that Windows Authentication implies Impersonation. When a user authenticates to a server with Windows Integrated auth, the code on the server doesn't automatically start running as that user.
The normal configuration for an Enterprise web app is for IIS to user Windows Auth to authenticate the users, but to connect to SQL Server using the IIS App Pool Identity.
If you want the users to connect as themselves to SQL Server through the web application, that's called "Impersonation" and you need to enable and configure it. There's a SO question here that shows how to perform impersonation in your middleware. But additional confgiuration, like Kerberos Constrained Delegation configuration may be required.
The downsides of doing this are
It's extra work to configure, and you may need your network admins to help.
If users can connect directly to the database through the app, they can do it through other tools too. So security administration is harder, and riskier.
Users can't reuse connections, so you'll have lots of idle connections.

HttpRequestMessage.GetClientCertificate() returns null in ASP Web API DelegatingHandler

I have an ASP Web Api (.Net Framework 4.6.1) which accepts client certificates. The requirement is to send a custom validation message in the response of a request that has an invalid certificate.
For example, if the certificate is missing I should send back "Client certificate is missing", if the OCSP validation fails, I should send back "Certificate has been revoked", etc.
This is the code:
public class CertificateMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var certificate = request.GetClientCertificate();
}
}
I have a client application where I select what certificate I want to use, and it does a request to the web api application (which is hosted on another machine). If the certificate is valid, then request.GetClientCertificates() returns the certificate, otherwise, if the certificate is expired or self-signed, request.GetClientCertificates() return null.
I have disable the automatic CLR validation by the IIS:
netsh http show sslcert
netsh http delete sslcert ipport=0.0.0.0:443
netsh http add sslcert ipport=0.0.0.0:443 e104e... appid={4dc3e181-...} certstorename=My verifyclientcertrevocation=disable
I have set:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\SslBindingInfo\0.0.0.0:443\DefaultSslCertCheckMode=1
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\SendTrustedIssuerList=0
None of the above settings worked.
Note: a 3rd party that uses the web app might send a self-signed certificate and the business logic should reject the request for such certificates, therefore, the inclusion of the CA, that was used to sign the certificate, in the Trusted Root Store, isn't possible.
Any help is appreciated on how to get the client certificate from the request.
EDIT: it seems that the module "IIS Web Core" validates the certificate against the certificates store way before the request is "forwarded" by IIS to my application:
You could try to check the below settings:
set the iis SSL setting to accept:
and set below code in web.config file:
<iisClientCertificateMappingAuthentication enabled="true">
</iisClientCertificateMappingAuthentication>
Edit:
Asp.net Core is a framework and Hostable Web Core (known as HWC) is a new concept in IIS to host a website/web services inside your own process. In short a smaller hosted version of IIS (an IIS express edition?).
This is accomplished by making a LoadLibrary call to load hwebcore.dll (%systemdrive%\Windows\System32\inetsrv\hwebcore.dll)
Try to disable the Hostable Web Core feature by following below steps:
Open control panel.
Click on “Turn Windows features on or off” from the left pane.
Locate Internet Information Services(IIS) Hostable Web Core from the list and uncheck the checkbox.
restart iis after doing changes.
https://blogs.iis.net/sukesh/iis7-hosted-web-core-custom-service-webcoreservice
refer this below links or more detail:
HttpRequestMessage.GetClientCertificate() returns null in Web API
How to use a client certificate to authenticate and authorize in a Web API
Client Authentication for WebAPI 2

.NET Core JWTBearer skip self-signed certificate validation for local communication with identity server

I have two API projects, one that's based on the .NET Framework 4.6.2 (an old API) and one that's based on .NET Core 2.0. The old API can disable self-signed certificate validation quite simply:
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
However this code does not work in .NET Core (even though it compiles fine), and it is causing me issues when the API tries to communicate (specifically when it tries to get the discovery document from the identity server, which is behind a reverse proxy which, in this environment, has a self-signed certificate that it uses for https). I've seen similar questions on StackOverflow but all the answers have to do with how the HttpClient is created. The issue for me is that I have no control over how the HttpClient is created (the token validation is middleware and I don't see any way to control how it's creating the HttpClient), so I'm wondering if there is a global way in .NET Core to skip self-signed certificate validation?
EDIT: my issue seems closely related to this issue, however I have taken the self-signed certificated and loaded it into the trusted root of both the container (where the api is running) and my local machine (where the container is running) and I still get the certificate validation errors.
I should have looked more closely at the JwtBearerOptions, turns out I could set options.BackchannelHttpHandler, e.g.: options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = delegate { return true; } }; -- now it works as expected.
EDIT: although it is possible to skip certificate validation in .NET Core, I eventually abandoned this approach because it became too cumbersome to find all the components that had an HttpClient and to modify the HttpClient to skip cert validation. The approach I eventually went with was to create a CA using easy-rsa and then to generate certs signed by the CA. Then the only step is to import the CA cert into the containers and they'll trust the other certs. It may sound like a lot but the easy-rsa command interface is fairly straight-forward and it really doesn't end up being that much effort.
Thanks to #riqitang.
I had same issue when using IdentityServer and self-signed certificate in asp.net core 5. I got following exception frequently every where authorization was required:
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://localhost:5443/.well-known/openid-configuration'.
finally adding following at ConfigureServices method, solves my issue.
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
...
options.JwtBackChannelHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
(message, certificate, chain, sslPolicyErrors) => true
};
});

Using Identity Server 3, ClaimsPrinciple null even after successful bearer token authentication

I have a test console app which I'm pointing at a local instance of Identity Server 3 to request an access token. The following code does this and returns my token fine (passing a single scope "scope.test.client").
static TokenResponse GetClientToken(string clientId, string clientSecret, string[] scopes)
{
var uri = new Uri(string.Concat(ID_BASE_URI, ID_URL_TOKEN));
var client = new TokenClient(
uri.AbsoluteUri,
clientId,
clientSecret);
return client.RequestClientCredentialsAsync(string.Join(" ", scopes)).Result;
I then use this token to call an API also running locally. This takes the TokenResponse obtained above and passed it to this method:
static void CallApi(string url, TokenResponse response)
{
try
{
using (var client = new HttpClient())
{
client.SetBearerToken(response.AccessToken);
Console.WriteLine(client.GetStringAsync(url).Result);
}
}
catch (Exception x)
{
Console.WriteLine(string.Format("Exception: {0}", x.Message));
}
}
The API (an ASP.NET WebApi project) uses an Owin Startup class to enforce bearer token authentication for all requests:
appBuilder.Map(baseApiUrl, inner =>
{
inner.UseWebApi(GlobalConfiguration.Configuration);
// Enforce bearer token authentication for all API requests
inner.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://identityserver/core",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "scope.test.client" }
});
});
It also ensures all API requests are handled by a custom authorize attribute:
GlobalConfiguration.Configuration.Filters.Add(new DefaultApiAuthorizeAttribute());
Debugging this API, the first line in my overridden OnAuthorize method (in DefaultApiAuthorizeAttribute) is this:
var caller = actionContext.RequestContext.Principal as System.Security.Claims.ClaimsPrincipal;
If I break on this line I can see that actionContext.RequestContext.Principal is always null. However, I can see that ((System.Web.Http.Owin.OwinHttpRequestContext)actionContext.RequestContext).Request.Headers contains an Authorization header with the bearer token passed from my console app.
So it would seem that the API project is not authenticating the bearer token. Certainly the Identity Server logs suggest it isn't being hit at all after issuing the initial access token. So I'd appreciate your expert advice about why this might not be happening, or at least some pointers about where to look.
I suspect it might have something to do with SSL. Both sites are hosted locally under self-signed SSL certs, although Identity Server is configured to not require SSL and uses the idsrv3test.pfx development certificate for signing. I do have another test MVC web app which delegates authentication to the same IS3 instance which works fine locally, so I believe my IS3 instance is configured correctly.
You need to call UseIdentityServerBearerTokenAuthentication before you call UseWebApi. When you set up an OWIN Middleware Pipeline, the order is important.
In your case, Web API will be handling your requests before they get sent onto Identity Server (if they get sent on at all).
I imagine a range of possible issues could have the impact I described, but in my case I was able to find the cause by adding a diagnostics log to my consuming API. This led me to discover that the problem was an assembly conflict. The Owin middleware was looking for a Newtonsoft.JSON assembly with version 8.0.0.0 but my consuming API (actually running on top of a CMS intance) was using 7.0.0.0.
For anyone else who wants to find the answer fast, rather than spend hours tweaking configurations, here's the documentation that describes how to add this logging: https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html

Trusting an IIS self-issued certificate

So I'm using a self-signed certificate on my localhost for HTTPS.
I'm running my Web Api 2 web service on it for testing while I develop a client website that links into the api but the client website can't establish a connection with the api because of trust issues concerning the certificate is self-signed and thus, not to be trusted.
Here's what Firefox says when I browse to the web service:
The client website is developed using MVC. So far I'm using WebClient to query data from the web service.
The code I'm currently working on to access the web server is a simple login. The MVC site sends the login data to the web service which uses it to select a record from the database. It sends the record back to the MVC site if it gets one.
Here's the code:
private bool DoLogin(string EmailAddress, string Password)
{
WebClient client = new WebClient();
NameValueCollection credentials = new NameValueCollection
{
{ "EmailAddress", EmailAddress },
{ "Password", Password }
};
client.QueryString = credentials;
string result = client.DownloadString(new Uri("https://localhost/mywebservice/api/User/"));
// just return true so we can debug to see values
return true;
}
Currently I'm only getting a non-descript WebException that just says "Internal Server Error" (Status Code 503).
Its my understanding now that I need to use a certificate that isn't self-signed, but I can't create one in IIS. I'm trying to create a domain certificate but I'm confused about the Certification Authority and from what I understand, this isn't going to help me get my web app to accept the certificate anyway.
Anyway, as an alternative, I'm looking at this MSDN blog and I've done all those things to get it right (see screenshot below) but it doesn't seem to have helped anything as I still get the untrusted connection screen in my browser.
I'm pretty much at a loss what I should do now...

Resources