Skip to content

Commit 3163d9e

Browse files
authored
Adding HttpClient examples to send the certificate (cert_auth.md) (dotnet#16354)
* Adding HttpClient examples to send the certificate * removing this as this is an Azure example * Using FromBase64String for Azure example * removing this as it is not need for Azure * updating PR, Azure needs no forwarding, default covered by middleware, example for a custom proxy added * Added missing comma * adding a note about CertificateForwardingMiddleware which is required for Azure certificate forwarding
1 parent b13b987 commit 3163d9e

1 file changed

Lines changed: 73 additions & 31 deletions

File tree

aspnetcore/security/authentication/certauth.md

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -231,19 +231,26 @@ See the [host and deploy documentation](xref:host-and-deploy/proxy-load-balancer
231231

232232
### Use certificate authentication in Azure Web Apps
233233

234+
No forwarding configuration is required for Azure. This is already setup in the certificate forwarding middleware.
235+
236+
> [!NOTE]
237+
> This requires that the CertificateForwardingMiddleware is present.
238+
239+
### Use certificate authentication in custom web proxies
240+
234241
The `AddCertificateForwarding` method is used to specify:
235242

236243
* The client header name.
237244
* How the certificate is to be loaded (using the `HeaderConverter` property).
238245

239-
In Azure Web Apps, the certificate is passed as a custom request header named `X-ARR-ClientCert`. To use it, configure certificate forwarding in `Startup.ConfigureServices`:
246+
In custom web proxies, the certificate is passed as a custom request header, for example `X-SSL-CERT`. To use it, configure certificate forwarding in `Startup.ConfigureServices`:
240247

241248
```csharp
242249
public void ConfigureServices(IServiceCollection services)
243250
{
244251
services.AddCertificateForwarding(options =>
245252
{
246-
options.CertificateHeader = "X-ARR-ClientCert";
253+
options.CertificateHeader = "X-SSL-CERT";
247254
options.HeaderConverter = (headerValue) =>
248255
{
249256
X509Certificate2 clientCertificate = null;
@@ -321,46 +328,81 @@ namespace AspNetCoreCertificateAuthApi
321328
}
322329
```
323330

324-
#### Implement an HttpClient using a certificate
331+
#### Implement an HttpClient using a certificate and the HttpClientHandler
325332

326-
The web API client uses an `HttpClient`, which was created using an `IHttpClientFactory` instance. This doesn't provide a way to define a handler for the `HttpClient`, so use an `HttpRequestMessage` to add the certificate to the `X-ARR-ClientCert` request header. The certificate is added as a string using the `GetRawCertDataString` method.
333+
The HttpClientHandler could be added directly in the constructor of the HttpClient class. Care should be taken when creating instances of the HttpClient. The HttpClient will then send the certificate with each request.
327334

328335
```csharp
329-
private async Task<JsonDocument> GetApiDataAsync()
336+
private async Task<JsonDocument> GetApiDataUsingHttpClientHandler()
330337
{
331-
try
338+
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
339+
var handler = new HttpClientHandler();
340+
handler.ClientCertificates.Add(cert);
341+
var client = new HttpClient(handler);
342+
343+
var request = new HttpRequestMessage()
332344
{
333-
// Do not hardcode passwords in production code
334-
// Use thumbprint or key vault
335-
var cert = new X509Certificate2(
336-
Path.Combine(_environment.ContentRootPath,
337-
"sts_dev_cert.pfx"), "1234");
338-
var client = _clientFactory.CreateClient();
339-
var request = new HttpRequestMessage()
340-
{
341-
RequestUri = new Uri("https://localhost:44379/api/values"),
342-
Method = HttpMethod.Get,
343-
};
345+
RequestUri = new Uri("https://localhost:44379/api/values"),
346+
Method = HttpMethod.Get,
347+
};
348+
var response = await client.SendAsync(request);
349+
if (response.IsSuccessStatusCode)
350+
{
351+
var responseContent = await response.Content.ReadAsStringAsync();
352+
var data = JsonDocument.Parse(responseContent);
353+
return data;
354+
}
355+
356+
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
357+
}
358+
```
344359

345-
request.Headers.Add("X-ARR-ClientCert", cert.GetRawCertDataString());
346-
var response = await client.SendAsync(request);
360+
#### Implement an HttpClient using a certificate and a named HttpClient from IHttpClientFactory
347361

348-
if (response.IsSuccessStatusCode)
349-
{
350-
var responseContent = await response.Content.ReadAsStringAsync();
351-
var data = JsonDocument.Parse(responseContent);
362+
In the following example, a client certificate is added to a HttpClientHandler using the ClientCertificates property from the handler. This handler can then be used in a named instance of a HttpClient using the ConfigurePrimaryHttpMessageHandler method. This is setup in the Startup class in the
363+
ConfigureServices method.
352364

353-
return data;
354-
}
365+
```csharp
366+
var clientCertificate =
367+
new X509Certificate2(
368+
Path.Combine(_environment.ContentRootPath, "sts_dev_cert.pfx"), "1234");
369+
370+
var handler = new HttpClientHandler();
371+
handler.ClientCertificates.Add(clientCertificate);
372+
373+
services.AddHttpClient("namedClient", c =>
374+
{
375+
}).ConfigurePrimaryHttpMessageHandler(() => handler);
376+
```
355377

356-
throw new ApplicationException(
357-
$"Status code: {response.StatusCode}, " +
358-
$"Error: {response.ReasonPhrase}");
359-
}
360-
catch (Exception e)
378+
The IHttpClientFactory can then be used to get the named instance with the handler and the certificate. The CreateClient method with the name of the client defined in the Startup class is used to get the instance. The HTTP request can be sent using the client as required.
379+
380+
```csharp
381+
private readonly IHttpClientFactory _clientFactory;
382+
383+
public ApiService(IHttpClientFactory clientFactory)
384+
{
385+
_clientFactory = clientFactory;
386+
}
387+
388+
private async Task<JsonDocument> GetApiDataWithNamedClient()
389+
{
390+
var client = _clientFactory.CreateClient("namedClient");
391+
392+
var request = new HttpRequestMessage()
393+
{
394+
RequestUri = new Uri("https://localhost:44379/api/values"),
395+
Method = HttpMethod.Get,
396+
};
397+
var response = await client.SendAsync(request);
398+
if (response.IsSuccessStatusCode)
361399
{
362-
throw new ApplicationException($"Exception {e}");
400+
var responseContent = await response.Content.ReadAsStringAsync();
401+
var data = JsonDocument.Parse(responseContent);
402+
return data;
363403
}
404+
405+
throw new ApplicationException($"Status code: {response.StatusCode}, Error: {response.ReasonPhrase}");
364406
}
365407
```
366408

0 commit comments

Comments
 (0)