C# Flurl - Add WebRequestHandler to FlurlClient - c#

I am working with Flurl to hit an API that requires certificate-based authentication. I have seen from this SO post that adding a certificate to a WebRequestHandler and instructing an HttpClient to use this handler is easy.
However, it is not so clear for me using Flurl. I have tried the follwing three things.
Extending DefaultHttpFactory
I first suspected that I needed to create my own X509HttpFactory : DefaultHttpFactory which would create the handler and assign it to the HttpClient. However, in viewing the source, I notice that the constructor for CreateClient already expects a handler. Where does this handler come from?
Creating Client using DefaultHttpFactory
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificates.Add(myX509Cert);
var clientFactory = new DefaultHttpClientFactory();
FlurlClient fc = clientFactory.CreateClient(url, handler);
This does not compile as HttpClient cannot be casted to FlurlClient
Use ConfigureHttpClient
var clientFactory = new DefaultHttpClientFactory();
FlurlClient fc = new Url("foobar.com").ConfigureHttpClient(client => client = clientFactory
.CreateClient(url, handler));
This seems like the most viable option, but I'm unsure since the delegate is an Action with no return type.
The Question
What is the best/correct way to support client-side certificate authentication using Flurl?

You're close - a custom factory is definitely the way to go. But you want to override CreateMessageHandler rather than CreateClient:
public class X509HttpFactory : DefaultHttpClientFactory
{
private readonly X509Certificate2 _cert;
public X509HttpFactory(X509Certificate2 cert) {
_cert = cert;
}
public override HttpMessageHandler CreateMessageHandler() {
var handler = base.CreateMessageHandler();
handler.ClientCertificates.Add(_cert);
return handler;
}
}
Then you can either register it globally (on app startup):
FlurlHttp.Configure(settings => {
settings.HttpClientFactory = new X509HttpFactory(myCert);
});
Or for all calls to a particular host:
FlurlHttp.ConfigureClient(ROOT_URL, cli => {
cli.Settings.HttpClientFactory = new X509HttpFactory(myCert);
});
Or for a new FlurlClient:
var cli = new FlurlClient(url)
.Configure(settings => settings.HttpClientFactory = new X509HttpFactory(myCert));
Or an existing one:
cli.Settings.HttpClientFactory = new X509HttpFactory(myCert);

Related

How to use ConfigurePrimaryHttpMessageHandler generic

I want to add an HttClientHandler for a Typed HttpClient in order to include certificate authentication.
All the examples I'm finding on the internet are like this:
services.AddHttpClient<IMyService, MyService>()
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
// Set here whatever you need to get configured
};
});
But I don't want to include all the logic to obtain the certificate here, so I would like to use the generic version of ConfigurePrimaryHttpMessageHandler<> and write my own message handler to include the certificate in the request.
The problem is that I'm struggling to understand how should I implement the message handler... should I inherit from HttpClientHandler ??
Help, please!
Update
As I initially suspected, and #Nkosi confirmed, to derive from HttpClient handler is the way to go in this scenario. The code in the ends looks similar to this:
public class MyHttpClientHandler : HttpClientHandler
{
private readonly IMyConfiguration _myConfiguration;
public MyHttpClientHandler(IMyConfiguration myConfiguration)
{
_myConfiguration = myConfiguration;
using (var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(
X509FindType.FindBySerialNumber,
_myConfiguration.MyCertificateSerial,
true);
X509Certificate2 certificate = certCollection[0];
ClientCertificateOptions = ClientCertificateOption.Manual;
SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
ClientCertificates.Add(certificate);
}
}
}
Very important!
On the other hand while trying to register my http client handler I noticed it was never being called. After some googling I found out that currently there's an open bug about that (https://github.com/aspnet/Extensions/issues/851). So until it get's fixed you need configure your handler this way:
services.AddTransient<MyHttpClientHandler>();
services.AddHttpClient<IMyService, MyService>()
.ConfigurePrimaryHttpMessageHandler(sp => sp.GetRequiredService<MyHttpClientHandler>());
Derive from HttpClientHandler or any HttpMessageHandler derived class.
public class MyHttpClientHandler : HttpClientHandler {
public MyHttpClientHandler() {
//Set here whatever you need to get configured
}
//...override members as needed
}
Call your handler using appropriate extension
services
.AddHttpClient<IMyService, MyService>()
.ConfigurePrimaryHttpMessageHandler<MyHttpClientHandler>();
The MyHttpClientHandler will be resolved from a scoped service provider that shares the lifetime of the handler being constructed.

Can I always create new instance of IHttpClientFactory even I can use instance twice or more?

I wrote a WebAPI in .NET Core and used IHttpClientFactory for creating httpClients. I customized it on Startup.cs in ConfigurationServices.
services.AddHttpClient("ITSMServiceClient", m =>
{
m.DefaultRequestHeaders.Accept.Clear();
m.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
}).SetHandlerLifetime(TimeSpan.FromMinutes(10))
.ConfigurePrimaryHttpMessageHandler(() =>
{
var handler = new HttpClientHandler()
{
AllowAutoRedirect = false,
UseCookies = false
};
// Отключаем проверку валидности http-сертификата (если необходимо)
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) => true;
return handler;
});
Then I use it in one dataProvider via DI.
private readonly IHttpClientFactory _clientFactory;
public ExecuteDataProvider(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
I use methods of this provider for all sending requests
public async Task<HttpResponseMessage> PostAsync(HttpRequestMessage request)
{
using (var client = _clientFactory.CreateClient("ITSMServiceClient"))
{
return await client.SendAsync(request);
}
}
And I have a question. Is it normal behavior always get new instance from _clientFactory even if I can reuse instance twice or more in code?
All my dataProvider and another entyties describe like SingleInstance in DI.
builder.RegisterType<ExecuteDataProvider>().As<IExecuteDataProvider>().SingleInstance();
Now I use it something like this:
var request = CreatePostRequest(url, parameters, jParameters, checkingToken);
HttpResponseMessage httpResponseMessage = await _executeCoreDataProvider.PostAsync(request);
if (await CheckingForUnauthorizedToken(httpResponseMessage, checkingToken))
{
request = CreatePostRequest(url, parameters, jParameters, checkingToken);
httpResponseMessage = await _executeCoreDataProvider.PostAsync(request);
}
response = await httpResponseMessage.Content.ReadAsStringAsync();
httpResponseMessage.Dispose();
And I worry about twice getting HttpClient. Is it normal or use one instance of client will be more correctly?
I've already read about ClientFactory on Microsoft Docs; this, and also some other sites.
In the scope of a single request, it's normal to use just one HttpClient. However, if it's easier, you can get multiple instances from the IHttpClientFactory. The HttpClient itself is just a thin wrapper, so multiple instances don't have a big impact. The important part is that these instances are coming from the IHttpClientFactory, which shares the HttpMessageHandler between multiple HttpClient instances.
The only unusual part to your code is the using statement; generally, HttpClient instances are not disposed (this is still true in the IHttpClientFactory world). But this disposal isn't bad; just a bit unusual.
Docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.1#httpclient-and-lifetime-management

How do I use Windows Authentication with the Flurl library?

Flurl has methods for doing OAuth and Basic authentication:
await url.WithBasicAuth("username", "password").GetJsonAsync();
await url.WithOAuthBearerToken("mytoken").GetJsonAsync();
but how do I do Windows authentication using the currently logged in user? The HttpClientHandler that Flurl is built on top of has a property UseDefaultCredentials but I don't know how to utilize that within Flurl.
var httpClient = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
Flurl intelligently reuses the HttpClientHandler for each domain, so you don't want to set the UseDefaultCredentials each time it runs. Instead, you can modify the HttpClientFactory to return one that's configured to UseDefaultCredentials.
public class UseDefaultCredentialsClientFactory : DefaultHttpClientFactory
{
public override HttpMessageHandler CreateMessageHandler()
{
return new HttpClientHandler { UseDefaultCredentials = true };
}
}
Then you need to tell Flurl to use this factory for the domains you want to use Windows authentication for.
public static class FlurlConfiguration
{
public static void ConfigureDomainForDefaultCredentials(string url)
{
FlurlHttp.ConfigureClient(url, cli =>
cli.Settings.HttpClientFactory = new UseDefaultCredentialsClientFactory());
}
}
Then you simply need to call this once on startup for each domain. For ASP.NET, the Application_Start method in your global application class is a good place for it.
FlurlConfiguration.ConfigureDomainForDefaultCredentials("https://example.com");
FlurlConfiguration.ConfigureDomainForDefaultCredentials("http://services.example.com");
Credit goes to Todd Menier for explaining this to me.
If it is still relevant. You can set credentials, something like this
((HttpClientHandler)url.Client.HttpMessageHandler).Credentials = new NetworkCredential(userName, password);
Create HttpClientHandler with your credentials and pass it to HttpClient then to FlurlClient like this:
var clientHandler = new HttpClientHandler
{
Credentials = new NetworkCredential("admin",
"bm8gcGFzc3dvcmQgaGVyZSB5b3UgZnVja2VyIQ==")
};
var httpClient = new HttpClient(clientHandler);
var client = new FlurlClient(httpClient);
var resp = await url.WithClient(client).GetAsync();

Bypassing SSL Certificate Validation on DotNet Core Service Stack

I know that ServicePointManager.ServerCertificateValidationCallback no longer exists in .Net Core and is instead replaced with:
using(var handler = new System.Net.Http.HttpClientHandler())
{
using (var httpClient = new System.Net.Http.HttpClient(handler))
{
handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
return true;
};
}
}
However we are currently using the ServiceStack.Core library which, as far as I can see, does not expose either a property like this or the handler itself.
How would I tell a ServiceStack client to bypass ssl validation in this code?
using(var client = new JsonServiceClient("https://www.google.com"))
{
var response = client.Get("/results");
}
If there is a way, would this work the same on both Windows and Linux?
JsonServiceClient is built on .NET HttpWebRequest which has been rewritten in .NET Core as a wrapper over HttpClient so we'd generally recommend for .NET Core to avoid this overhead (which is much slower than .NET 4.5) and switch to using JsonHttpClient in ServiceStack.HttpClient instead as it uses HttpClient directly and where you can inject your own HttpClientHandler with:
var client = new JsonHttpClient(baseUrl)
{
HttpMessageHandler = new HttpClientHandler
{
UseCookies = true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ServerCertificateCustomValidationCallback = (req,cert,chain,errors) => true
}
};
Note it's recommended to reuse HttpClient instances so you should try to reuse HttpClient instances when possible and avoid disposing them.

Allowing Untrusted SSL Certificates with HttpClient

I'm struggling to get my Windows 8 application to communicate with my test web API over SSL.
It seems that HttpClient/HttpClientHandler does not provide and option to ignore untrusted certificates like WebRequest enables you to (albeit in a "hacky" way with ServerCertificateValidationCallback).
Any help would be much appreciated!
A quick and dirty solution is to use the ServicePointManager.ServerCertificateValidationCallback delegate. This allows you to provide your own certificate validation. The validation is applied globally across the whole App Domain.
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
I use this mainly for unit testing in situations where I want to run against an endpoint that I am hosting in process and am trying to hit it with a WCF client or the HttpClient.
For production code you may want more fine grained control and would be better off using the WebRequestHandler and its ServerCertificateValidationCallback delegate property (See dtb's answer below). Or ctacke answer using the HttpClientHandler. I am preferring either of these two now even with my integration tests over how I used to do it unless I cannot find any other hook.
If you're attempting to do this in a .NET Standard library, here's a simple solution, with all of the risks of just returning true in your handler. I leave safety up to you.
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
var client = new HttpClient(handler);
Have a look at the WebRequestHandler Class and its ServerCertificateValidationCallback Property:
using (var handler = new WebRequestHandler())
{
handler.ServerCertificateValidationCallback = ...
using (var client = new HttpClient(handler))
{
...
}
}
If you are using System.Net.Http.HttpClient I believe correct pattern is
var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
var http = new HttpClient(handler);
var res = http.GetAsync(url);
Link to official doc
Most answers here suggest to use the typical pattern:
using (var httpClient = new HttpClient())
{
// do something
}
because of the IDisposable interface. Please don't!
Microsoft tells you why:
Improper Instantiation antipattern
HttpClient, HttpClientHandler, and WebRequestHandler Explained
And here you can find a detailed analysis whats going on behind the scenes:
You're using HttpClient wrong and it is destabilizing your software
Official Microsoft link: HttpClient
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
Regarding your SSL question and based on Improper Instantiation antipattern # How to fix the problem
Here is your pattern:
class HttpInterface
{
// https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem
// https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient#remarks
private static readonly HttpClient client;
// static initialize
static HttpInterface()
{
// choose one of these depending on your framework
// HttpClientHandler is an HttpMessageHandler with a common set of properties
var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = delegate { return true; },
};
// derives from HttpClientHandler but adds properties that generally only are available on full .NET
var handler = new WebRequestHandler()
{
ServerCertificateValidationCallback = delegate { return true; },
ServerCertificateCustomValidationCallback = delegate { return true; },
};
client = new HttpClient(handler);
}
.....
// in your code use the static client to do your stuff
var jsonEncoded = new StringContent(someJsonString, Encoding.UTF8, "application/json");
// here in sync
using (HttpResponseMessage resultMsg = client.PostAsync(someRequestUrl, jsonEncoded).Result)
{
using (HttpContent respContent = resultMsg.Content)
{
return respContent.ReadAsStringAsync().Result;
}
}
}
Or you can use for the HttpClient in the Windows.Web.Http namespace:
var filter = new HttpBaseProtocolFilter();
#if DEBUG
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
#endif
using (var httpClient = new HttpClient(filter)) {
...
}
With Windows 8.1, you can now trust invalid SSL certs. You have to either use the Windows.Web.HttpClient or if you want to use the System.Net.Http.HttpClient, you can use the message handler adapter I wrote:
http://www.nuget.org/packages/WinRtHttpClientHandler
Docs are on the GitHub:
https://github.com/onovotny/WinRtHttpClientHandler
If this is for a Windows Runtime application, then you have to add the self-signed certificate to the project and reference it in the appxmanifest.
The docs are here:
http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx
Same thing if it's from a CA that's not trusted (like a private CA that the machine itself doesn't trust) -- you need to get the CA's public cert, add it as content to the app then add it to the manifest.
Once that's done, the app will see it as a correctly signed cert.
Use this in Startup.cs for ASP.NET Core project:
public void ConfigureServices(IServiceCollection services)
{
// other code
services
.AddHttpClient<IMyService, MyService>(client =>
{
client.BaseAddress = new Uri(myConfiguration.BaseUrl);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
// Allowing Untrusted SSL Certificates
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) => true;
return handler;
});
}
I found an example in this Kubernetes client where they were using X509VerificationFlags.AllowUnknownCertificateAuthority to trust self-signed root certificates. I slightly reworked their example to work with our own PEM encoded root certificates. Hopefully this helps someone.
namespace Utils
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
/// <summary>
/// Verifies that specific self signed root certificates are trusted.
/// </summary>
public class HttpClientHandler : System.Net.Http.HttpClientHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientHandler"/> class.
/// </summary>
/// <param name="pemRootCerts">The PEM encoded root certificates to trust.</param>
public HttpClientHandler(IEnumerable<string> pemRootCerts)
{
foreach (var pemRootCert in pemRootCerts)
{
var text = pemRootCert.Trim();
text = text.Replace("-----BEGIN CERTIFICATE-----", string.Empty);
text = text.Replace("-----END CERTIFICATE-----", string.Empty);
this.rootCerts.Add(new X509Certificate2(Convert.FromBase64String(text)));
}
this.ServerCertificateCustomValidationCallback = this.VerifyServerCertificate;
}
private bool VerifyServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// add all your extra certificate chain
foreach (var rootCert in this.rootCerts)
{
chain.ChainPolicy.ExtraStore.Add(rootCert);
}
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = chain.Build((X509Certificate2)certificate);
var rootCertActual = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
var rootCertExpected = this.rootCerts[this.rootCerts.Count - 1];
isValid = isValid && rootCertActual.RawData.SequenceEqual(rootCertExpected.RawData);
return isValid;
}
// In all other cases, return false.
return false;
}
private readonly IList<X509Certificate2> rootCerts = new List<X509Certificate2>();
}
}
I don't have an answer, but I do have an alternative.
If you use Fiddler2 to monitor traffic AND enable HTTPS Decryption, your development environment will not complain. This will not work on WinRT devices, such as Microsoft Surface, because you cannot install standard apps on them. But your development Win8 computer will be fine.
To enable HTTPS encryption in Fiddler2, go to Tools > Fiddler Options > HTTPS (Tab) > Check "Decrypt HTTPS Traffic".
I'm going to keep my eye on this thread hoping for someone to have an elegant solution.
I found an example online which seems to work well:
First you create a new ICertificatePolicy
using System.Security.Cryptography.X509Certificates;
using System.Net;
public class MyPolicy : ICertificatePolicy
{
public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request,
int certificateProblem)
{
//Return True to force the certificate to be accepted.
return true;
}
}
Then just use this prior to sending your http request like so:
System.Net.ServicePointManager.CertificatePolicy = new MyPolicy();
http://www.terminally-incoherent.com/blog/2008/05/05/send-a-https-post-request-with-c/
For Xamarin Android this was the only solution that worked for me: another stack overflow post
If you are using AndroidClientHandler, you need to supply a SSLSocketFactory and a custom implementation of HostnameVerifier with all checks disabled. To do this, you’ll need to subclass AndroidClientHandler and override the appropriate methods.
internal class BypassHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string hostname, ISSLSession session)
{
return true;
}
}
internal class InsecureAndroidClientHandler : AndroidClientHandler
{
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
{
return SSLCertificateSocketFactory.GetInsecure(1000, null);
}
protected override IHostnameVerifier GetSSLHostnameVerifier(HttpsURLConnection connection)
{
return new BypassHostnameVerifier();
}
}
And then
var httpClient = new System.Net.Http.HttpClient(new InsecureAndroidClientHandler());

Categories

Resources