'Make Https call using HttpClient

I have been using HttpClient for making WebApi calls using C#. Seems neat & fast way compared to WebClient. However I am stuck up while making Https calls.

How can I make below code to make Https calls?

HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://foobar.com/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/xml"));

var task = httpClient.PostAsXmlAsync<DeviceRequest>(
                "api/SaveData", request);

EDIT 1: The code above works fine for making http calls. But when I change the scheme to https it does not work. Here is the error obtained:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

EDIT 2: Changing the scheme to https is: step one.

How do I supply certificate & public / private key along with C# request.



Solution 1:[1]

If the server only supports higher TLS version like TLS 1.2 only, it will still fail unless your client PC is configured to use higher TLS version by default. To overcome this problem, add the following in your code:

System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

Modifying your code example, it would be

HttpClient httpClient = new HttpClient();   

//specify to use TLS 1.2 as default connection
System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

httpClient.BaseAddress = new Uri("https://foobar.com/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
    
var task = httpClient.PostAsXmlAsync<DeviceRequest>("api/SaveData", request);

Solution 2:[2]

Simply specify HTTPS in the URI.

new Uri("https://foobar.com/");

Foobar.com will need to have a trusted SSL cert or your calls will fail with untrusted error.

EDIT Answer: ClientCertificates with HttpClient

WebRequestHandler handler = new WebRequestHandler();
X509Certificate2 certificate = GetMyX509Certificate();
handler.ClientCertificates.Add(certificate);
HttpClient client = new HttpClient(handler);

EDIT Answer2: If the server you are connecting to has disabled SSL, TLS 1.0, and 1.1 and you are still running .NET framework 4.5(or below) you need to make a choice

  1. Upgrade to .Net 4.6+ (Supports TLS 1.2 by default)
  2. Add registry changes to instruct 4.5 to connect over TLS1.2 ( See: salesforce writeup for compat and keys to change OR checkout IISCrypto see Ronald Ramos answer comments)
  3. Add application code to manually configure .NET to connect over TLS1.2 (see Ronald Ramos answer)

Solution 3:[3]

There is a non-global setting at the level of HttpClientHandler:

var handler = new HttpClientHandler()
{
    SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};

var client = new HttpClient(handler);

Thus one enables latest TLS versions.

Note, that the default value SslProtocols.Default is actually SslProtocols.Ssl3 | SslProtocols.Tls (checked for .Net Core 2.1 and .Net Framework 4.7.1).

Update: In .Net 5.0 the default value for HttpClientHandler.SslProtocols is None whcih means the following (see docs):

Allows the operating system to choose the best protocol to use, and to block protocols that are not secure. Unless your app has a specific reason not to, you should use this field.

Solution 4:[4]

Your code should be modified in this way:

httpClient.BaseAddress = new Uri("https://foobar.com/");

You have just to use the https: URI scheme. There's a useful page here on MSDN about the secure HTTP connections. Indeed:

Use the https: URI scheme

The HTTP Protocol defines two URI schemes:

http : Used for unencrypted connections.

https : Used for secure connections that should be encrypted. This option also uses digital certificates and certificate authorities to verify that the server is who it claims to be.

Moreover, consider that the HTTPS connections use a SSL certificate. Make sure your secure connection has this certificate otherwise the requests will fail.

EDIT:

Above code works fine for making http calls. But when I change the scheme to https it does not work, let me post the error.

What does it mean doesn't work? The requests fail? An exception is thrown? Clarify your question.

If the requests fail, then the issue should be the SSL certificate.

To fix the issue, you can use the class HttpWebRequest and then its property ClientCertificate. Furthermore, you can find here a useful sample about how to make a HTTPS request using the certificate.

An example is the following (as shown in the MSDN page linked before):

//You must change the path to point to your .cer file location. 
X509Certificate Cert = X509Certificate.CreateFromCertFile("C:\\mycert.cer");
// Handle any certificate errors on the certificate from the server.
ServicePointManager.CertificatePolicy = new CertPolicy();
// You must change the URL to point to your Web server.
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://YourServer/sample.asp");
Request.ClientCertificates.Add(Cert);
Request.UserAgent = "Client Cert Sample";
Request.Method = "GET";
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();

Solution 5:[5]

When connect to https I got this error too, I add this line before HttpClient httpClient = new HttpClient(); and connect successfully:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

I know it from This Answer and Another Similar Anwser and the comment mentions:

This is a hack useful in development so putting a #if DEBUG #endif statement around it is the least you should do to make this safer and stop this ending up in production

Besides, I didn't try the method in Another Answer that use new X509Certificate() or new X509Certificate2() to make a Certificate, I'm not sure simply create by new() will work or not.

EDIT: Some References:

Create a Self-Signed Server Certificate in IIS 7

Import and Export SSL Certificates in IIS 7

Convert .pfx to .cer

Best practices for using ServerCertificateValidationCallback

I find value of Thumbprint is equal to x509certificate.GetCertHashString():

Retrieve the Thumbprint of a Certificate

Solution 6:[6]

I had the same problem when connecting to GitHub, which requires a user agent. Thus it is sufficient to provide this rather than generating a certificate

var client = new HttpClient();

client.BaseAddress = new Uri("https://api.github.com");
client.DefaultRequestHeaders.Add(
    "Authorization",
    "token 123456789307d8c1d138ddb0848ede028ed30567");
client.DefaultRequestHeaders.Accept.Add(
    new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add(
    "User-Agent",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");

Solution 7:[7]

Just specifying HTTPS in the URI should do the trick.

httpClient.BaseAddress = new Uri("https://foobar.com/");

If the request works with HTTP but fails with HTTPS then this is most certainly a certificate issue. Make sure the caller trusts the certificate issuer and that the certificate is not expired. A quick and easy way to check that is to try making the query in a browser.

You also may want to check on the server (if it's yours and / or if you can) that it is set to serve HTTPS requests properly.

Solution 8:[8]

I was also getting the error:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

... with a Xamarin Forms Android-targeting application attempting to request resources from an API provider that required TLS 1.3.

The solution was to update the project configuration to swap out the Xamarin "managed" (.NET) http client (that doesn't support TLS 1.3 as of Xamarin Forms v2.5), and instead use the android native client.

It's a simple project toggle in visual studio. See screenshot below.

  • Project Properties
  • Android Options
  • Advanced
  • List item
  • Change "HttpClient implementation" to "Android"
  • Change SSL/TLS implementation to "Native TLS 1.2+"

enter image description here

Solution 9:[9]

I had this issue and in my case the solution was stupidly simple: open Visual Studio with Administrator rights. I tried all the above solutions and it didn't work until I did this. Hope it saves someone some precious time.

Solution 10:[10]

Add the below declarations to your class:

public const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
public const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;

After:

var client = new HttpClient();

And:

ServicePointManager.SecurityProtocol = Tls12;
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 /*| SecurityProtocolType.Tls */| Tls12;

Happy? :)

Solution 11:[11]

You can try using the ModernHttpClient Nuget Package: After downloading the package, you can implement it like this:

 var handler = new ModernHttpClient.NativeMessageHandler()
 {
     UseProxy = true,
 };


 handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
 handler.PreAuthenticate = true;
 HttpClient client = new HttpClient(handler);

Solution 12:[12]

I agree with felickz but also i want to add an example for clarifying the usage in c#. I use SSL in windows service as follows.

    var certificatePath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin");
    gateway = new GatewayService();
    gateway.PreAuthenticate = true;


    X509Certificate2 cert = new X509Certificate2(certificatePath + @"\Attached\my_certificate.pfx","certificate_password");
    gateway.ClientCertificates.Add(cert);

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    gateway.UserAgent = Guid.NewGuid().ToString();
    gateway.Timeout = int.MaxValue;

If I'm going to use it in a web application, I'm just changing the implementation on the proxy side like this:

public partial class GatewayService : System.Web.Services.Protocols.SoapHttpClientProtocol // to => Microsoft.Web.Services2.WebServicesClientProtocol

Solution 13:[13]

Here is working code that works in HTTPS call too

UriBuilder builder = new UriBuilder("https://yourdomain.com/");
builder.Query = "id=10";                 
//Create a query
HttpClient client = new HttpClient();
System.Net.ServicePointManager.SecurityProtocol = 
SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;                  
client.DefaultRequestHeaders.Add("Authorization", userprofile.Token);                
var result = client.GetAsync(builder.Uri).Result;                                       
using (StreamReader sr = new 
StreamReader(result.Content.ReadAsStreamAsync().Result))
{                       
   responseres = sr.ReadToEnd();
}