Trusting an IIS self-issued certificate - asp.net

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

Related

.Net Core Web API with Client Certificate Authentication

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

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

HttpClient.GetAsync works with LocalHost API, but not Live API

I have a .Net 4.5.2 WebApp that is calling my API. When I point my web app to the LocalHost version of my API, it gets the data, and comes back just fine. I published that API, and confirm that the API is working correctly with PostMan.
Then I run the exact same WebApp code, changing only the URI from localhost to live api, and I get a multiple exception error consisting of the following:
An existing connection was forcibly closed by the remote host
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
The underlying connection was closed: An unexpected error occurred on a send.
An error occurred while sending the request.
Here's my calling code
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("user", serializedUser);
response = null;
try
{
//Uri uri = new Uri("https://jsonplaceholder.typicode.com/posts/1");//https works
Uri uri = new Uri("https://api.acme.com/values/test");
//Uri uri = new Uri("http://localhost/5000/values/test"); //http localhost works
response = client.GetAsync(uri).Result;
}
catch (Exception e)
{
string er = e.Message;
}
}
EDIT 1: I created a .NET Core app from scratch, and my original code works perfectly calling my live API. My original code also work in .NET 4.5.2 calling a different "https" API.
EDIT 2:
So this is where I'm at now, I have created two generic apps from VS 2015, one is a .NET Core Web App, the other a .NET Framework Web App. I have used the above code exactly the same in both apps to call the API. In both apps, I can call a generic "https" api I found online (jsonplaceholder). I can also call the localhost version of my app at "http" from both. In the .NET Core version of the app, I can call my "https" live API and get the results I'm looking for. In the .NET Framework app I still get the same errors.
I can't figure out what the difference is between my Core and Framework requests that is getting one shut down when the other isn't.
It seems you are hosting the application on secured http environment (https). Are you using SSL certificate on the server where you are hosting your Web API? If not, It might be throwing the certificate related exceptions.
Just add the following line before the call to GetAsync and This will ignore the SSL errors.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
This is only recommended in an intranet environment or other closed network where server identities can't be forged.
C# Ignore certificate errors?
Adding the following line before my API call fixed the issue, but I'd love to hear an explanation of what this line does, and any security risks this might impose using it in my web app.
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Props to this answer!

ASP.NET MVC with Forms Auth and WebApi with Basic Auth

I have a WebApi using Basic Auth nicely. And I have an MVC site using Forms Auth nicely. But here's the catch:
Client X has a dedicated database with any number of Contacts and Products. The MVC site is a dedicated site for them (via {clientId} routing), which allows their Contacts to log in (via Forms Auth) and place orders for their products. The Contact must be Form-ly logged in to place an order.
The product orders (need to) hit the WebApi to be recorded in the Client's database.
But since the WebApi uses Basic Auth to validate the Client, not the Contacts who placed the orders, every request comes back is 401 - Unauthorized.
I've checked out ThinkTecture as suggested by a number of posts here on SO, however it doesn't get me what I need because I'm not looking to allow Forms Auth in the WebApi. I don't want to authenticate the Contact from the Client's database in the WebApi, I want to authenticate the Client in the WebApi.
Has anyone come across a similar scenario and am I missing something glaringly obvious? Perhaps I need to implement both Forms and Basic on the site?
The very standard Api call I'm making from the site (where the UserName and Password are the Client's, not the Contact's):
var clientId = new Guid(RouteData.Values["clientId"].ToString());
var baseUrl = ConfigurationManager.AppSettings["ApiBaseAddress"];
var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(String.Format("{0}:{1}", _shoppingCartSettings.UserName, _shoppingCartSettings.Password)));
var requestUrl = String.Format("api/{0}/inventory", clientId.ToString());
var httpWebRequest = WebRequest.Create(baseUrl + requestUrl);
httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authHeader);
httpWebRequest.Method = "GET";
httpWebRequest.Accept = "application/json";
httpWebRequest.ContentType = "application/json";
try
{
using (var httpWebResponse = httpWebRequest.GetResponse())
{
// we never get here because of a 401
}
}
catch (WebException ex)
{
using (var httpWebResponse = ex.Response)
{
// we always get here
}
}
If I set up a separate test client and make the same call, it works great :/
Is your Web API under the same virtual directory and configuration as the MVC site? It looks like the Forms Auth HTTP module kicks in for your API, which you don't want. As long as you don't plan to call the API directly from the browser, move it to a separate virtual directory that is set up exclusively for basic auth, no forms auth module in the web.config for the API.
Why not have one login for your MVC site that has the ability to submit orders for every Client? It makes sense for your WebAPI to only allow Clients to submit orders for themselves. But I don't think it makes sense to have your MVC site authenticate as different Clients based on the Contact. Your MVC site would have to store the passwords for each Client.
Instead, create one login for the MVC site and give it the ability to submit an order for any Client.
After much banging of head against the not-so-proverbial wall, and a much needed shove by #0leg, I've discovered the cause.
In the Properties of my WebApi project file under Web > Servers, the Visual Studio Development Server was being used with a Virtual Path of "/", whereas my MVC project file was set up to use the Local IIS Web Server. The MVC project also had the Apply server settings to all users (store in project file) option checked.
Setting both to use the local IIS server resolved it.
Upon further contemplation, this now seems logical since they were essentially running on different servers.
Posting this for posterity's sake.

The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'NTLM'

This is driving me crazy.
I'm trying to access a WCF service via an ASP.NET web form,
I'm setting the binding for the channelfactory up like this:
BasicHttpBinding b = null;
if (Communication.SlServiceURL.StartsWith("https"))
b = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
else
{
b = new BasicHttpBinding();
b.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
}
b.MaxBufferSize = 2147483647;
b.MaxReceivedMessageSize = 2147483647;
b.Security.Transport = new HttpTransportSecurity { ClientCredentialType = HttpClientCredentialType.Ntlm, ProxyCredentialType = HttpProxyCredentialType.None };
b.Security.Message = new BasicHttpMessageSecurity { ClientCredentialType = BasicHttpMessageCredentialType.UserName };
Then I create a new Channel Wrapper via:
public ClientChannelWrapper(Binding binding, EndpointAddress remoteAddress)
{
m_Factory = new ChannelFactory<T>(binding, remoteAddress);
}
I'm passing the binding and a https://[myservice]/myservice.svc URL as the remoteAddress.
The thing is: When I call the service in my production server with IIS 7, Windows Authentication enabled on the site and ONLY "NTLM" as the provider for the authentication, everything else disabled (no anonymous, forms, etc.)
I get the exception:
Exception type: MessageSecurityException
Exception message: The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'NTLM'.
This is pretty weird and I can't seem to find a way around this. I have also a Silverlight application hosted in another ASP.NET Form and there it works fine with this binding which is freaking me out because why wouldn't the same thing work in the ASP.NET site hosted in the same IIS site.
Also strange: When firing the site up from Visual Studio with F5 it works when accessing the production WCF service in the IIS7 machine. As soon as I deploy the site I get the exception.
Edit: It's clear now why Silverlight behaves different than ASP.NET, since Silverlight is communicating directly via the client computer with the service. Still no luck with the exception.

Resources