The example code on this section seems to imply that you should configure a HttpClient to send along a client certificate for mutual TLS by including the client certificate as part of the _X-ARR-ClientCert_ header:
request.Headers.Add("X-ARR-ClientCert", cert.GetRawCertDataString());
This seems wrong since the appropriate way to do so is setting the certificate on the HttpClient's HttpClientHandler:
```
httpClientHandler.ClientCertificates.Add(certificate);
````
The confusion appears to come from the fact that an Azure web frontend server will forward the supplied client certificate to the the web app/api backend server in the X-ARR-ClientCert request header. The frontend web server however only does this after it verified that the client is in possession of the private key belonging to the certificate as part of the Certificate Verify phase of the TLS negotiation.
It seems to me that by sending the _X-ARR-ClientCert_ as part of the initial HttpClient request you run the risk that on an improperly configured system you can trick the backend web server from thinking a proper client certificate was presented by only sending the (public) client certificate, since no proof of possession check is performed. This might therefore cause authentication/authoriation bugs.
⚠Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.
Any ideas, @blowdart or @damienbod?
Hi @etortec @scottaddie @blowdart
I don't see the problem here. You can send the certificate either way. You rely on specific Azure logic, implementation which does proof of possession, but this should be done in the code, if you rely on this. Maybe you don’t want this, for example without Azure, local development etc. The example can be used anywhere, not only Azure, so I think it important to have an example. Maybe the 2 other more standard ways of sending certificates should be documented as well and the specific Azure use case could then be mentioned which forwards the certificate in this way, but should not be relied upon as the certificates can be sent directly in the header anyway.
I did a blog about how certificates can be sent in the HTTP requests using HTTPClient, maybe some of this could be added to extend this.
Greetings Damien
Hi @damienbod
As far as i know cert.GetRawCertDataString() simply returns the encoded (public) certificate without the private key. This implies no proof of possession checks can be done, and we are essentially authenticating with a shared secret where the secret is a (public) certificate. We are essentially doing a variant of basic authentication with a publicly known secret.
To the best of my knowledge proper implementation of mutual TLS in AspNetCore consists out of two steps:
X-ARR-ClientCert header).X-ARR-ClientCert header is actually a trusted certificate, granting or denying access as appropriate.The proof of possession check in step 1 is essential since otherwise a malicious client can trick AspNetCore in granting them access by sending the public(!) certificate along in the X-ARR-ClientCert header (assuming the server does not explicitly drop this header).
I strongly agree with the previous comment. There's only _one_ reliable way (and not a more standard way) to authenticate with a client certificate, which is using
httpClientHandler.ClientCertificates.Add(certificate);
X-ARR-ClientCert merely carries the public certificate as a means to transport identity, but not the proof of possession of the private key.
Other proxies (i.e. Envoy) use a different header name called x-forwarded-client-cert which highlights that this is a header-value in the family of the x-forwarded-* values, usually only passed by a proxy or load balancer.
The only use-case to send these header-values directly through a HttpClient should be testing if your application is correctly set up to function behind a load-balancer.
I also think it would be important to stress that when using a load-balancer/proxy which does the SSL negotiation for you there's no need to set up HTTPS in your application. It is briefly mentioned in the very beginning under "Proxy and load balancer scenarios", but should probably be repeated once the section "Azure and custom web proxies" is started.
@etortec @daniel-munch-cko
I added a note to the docs about this. If the server accepts certificates using a header parameter from a HTTP client, then the server needs to process the requests correctly as you mentioned.
This means for Azure, a HttpClient sending the cert using a header param needs to be rejected, or processed correctly.
Does this make sense?
I will also add a note about send the cert to Azure using:
httpClientHandler.ClientCertificates.Add(certificate);
Greetings Damien
@damienbod note the above when you use Fixes # issue

The sample is still outstanding, at least in 3.x code. @damienbod I missed this, for azure no you don't need to encode, ARR does that work. httpClientHandler.ClientCertificates.Add(certificate); is the only way to write a client. No encoding client side at all.
@blowdart Yes I removed this in the PR
Ah ok, is it just lacking my approval? (Would make sense, I took December off!)
Most helpful comment
Hi @damienbod
As far as i know
cert.GetRawCertDataString()simply returns the encoded (public) certificate without the private key. This implies no proof of possession checks can be done, and we are essentially authenticating with a shared secret where the secret is a (public) certificate. We are essentially doing a variant of basic authentication with a publicly known secret.To the best of my knowledge proper implementation of mutual TLS in AspNetCore consists out of two steps:
X-ARR-ClientCertheader).X-ARR-ClientCertheader is actually a trusted certificate, granting or denying access as appropriate.The proof of possession check in step 1 is essential since otherwise a malicious client can trick AspNetCore in granting them access by sending the public(!) certificate along in the
X-ARR-ClientCertheader (assuming the server does not explicitly drop this header).