How to attach client certificate to AspNetCore TestServer api? - asp.net-core

I'm looking at Microsoft.AspNetCore.TestHost.TestServer in the Microsoft source code. There I see the CreateClient() property that is HttpClient objects.
So how do I attached the client certificate for Digitial Signature in xUnit Test?
HttpClient example would be this.
var x509Certificate2 = new X509Certificate(); // Pretend it contains certificate data already.
var httpClientHandler = new HttpClientHandler() {
ClientCertificateOptions = ClientCertificateOption.Manual
};
httpClientHandler.ClientCertificates.Add(x509Certificate2);
using (var httpClient = new HttpClient(httpClientHandler))
{
}
Now using the TestServer
var testServer = new TestServer();
testServer.CreateClient(); // How do I attached client certificate here?
So, how do we attach the client certificate here? For CreateClient()
Also, I can try to make do with implementing the HttpRequestMessage but it doesn't support that certificate option either.

You may try to use the testServer.SendAsync(...) method and just construct your HttpContext from there.
var result = await server.SendAsync(context =>
{
context.Connection.ClientCertificate = myClientCertificate;
context.Request.Method = "POST";
});
Just make sure to specify the Path and the Body as needed.

Related

How to use WebProxy with RestSharp?

I would like to know how to use WebProxy with RestSharp. I am using version 108.0.1 and the code given below returns 407 when running locally from my workstation, which should use my credentials.
var client = new RestClient("https://www.google.com");
var proxy = new System.Net.WebProxy("http://mycorpproxy.com");
proxy.UseDefaultCredentials = true;
client.Options.Proxy = proxy;
var request = new RestRequest();
request.Method = Method.Get;
var response = client.Execute(request);
You need to specify the proxy in the options when you create the client, not after. In v107, the options object properties were init-only, but it fails on legacy SDKs, so we had to revert it to setters, but setting the options that are used to create an HttpClient instance after the client is created has no effect.
var proxy = new WebProxy("http://mycorpproxy.com") {
UseDefaultCredentials = true
};
var options = new RestClientOptions("https://www.google.com") {
Proxy = proxy
};
var client = new RestClient(options);

How to add JWT Bearer Token to ALL requests to an API

I'm in the process of trying to put together a small project which uses Asp.Net Core Identity, Identity Server 4 and a Web API project.
I've got my MVC project authenticating correctly with IdS4 from which I get a JWT which I can then add to the header of a request to my Web API project, this all works as expected.
The issue I have is how I'm actually adding the token to the HttpClient, basically I'm setting it up for every request which is obviously wrong otherwise I'd have seen other examples online, but I haven't been able to determine a good way to refactor this. I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!
Here is an example MVC action that calls my API:
[HttpGet]
[Authorize]
public async Task<IActionResult> GetFromApi()
{
var client = await GetHttpClient();
string testUri = "https://localhost:44308/api/TestItems";
var response = await client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();
GetFromApiViewModel vm = new GetFromApiViewModel()
{
Output = data
};
return View(vm);
}
And here is the GetHttpClient() method which I call (currently residing in the same controller):
private async Task<HttpClient> GetHttpClient()
{
var client = new HttpClient();
var expat = HttpContext.GetTokenAsync("expires_at").Result;
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
//SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
}
var accessToken = await HttpContext.GetTokenAsync("access_token");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return client;
}
My StartUp classes are pretty standard from what I gather, but if they could be useful, then I'll add them in.
I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!
The problem is that the docs are really spread all over, so it's hard to get a big picture of all the best practices. I'm planning a blog series on "Modern HTTP API Clients" that will collect all these best practices.
First, I recommend you use HttpClientFactory rather than new-ing up an HttpClient.
Next, adding an authorization header is IMO best done by hooking into the HttpClient's pipeline of message handlers. A basic bearer-token authentication helper could look like this:
public sealed class BackendApiAuthenticationHttpClientHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
public BackendApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var expat = await _accessor.HttpContext.GetTokenAsync("expires_at");
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
//SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
}
var token = await _accessor.HttpContext.GetTokenAsync("access_token");
// Use the token to make the call.
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
This can be hooked up via DI:
services.AddTransient<BackendApiAuthenticationHttpClientHandler>();
services.AddHttpClient<MyController>()
.ConfigureHttpClient((provider, c) => c.BaseAddress = new Uri("https://localhost:44308/api"))
.AddHttpMessageHandler<BackendApiAuthenticationHttpClientHandler>();
Then you can inject an HttpClient into your MyController, and it will magically use the auth tokens:
// _client is an HttpClient, initialized in the constructor
string testUri = "TestItems";
var response = await _client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();
GetFromApiViewModel vm = new GetFromApiViewModel()
{
Output = data
};
return View(vm);
This pattern seems complex at first, but it separates the "how do I call this API" logic from "what is this action doing" logic. And it's easier to extend with retries / circuit breakers / etc, via Polly.
You can use HttpRequestMessage
// Create this instance once on stratup
// (preferably you want to keep an instance per base url to avoid waiting for socket fin)
HttpClient client = new HttpClient();
Then create an instance of HttpRequestMessage:
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get,
"https://localhost:44308/api/TestItems");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "ey..");
await client.SendAsync(request);

Receive IFileForm in Net Core Controller and forward to another (independent) API

I have an Vue.JS application, where I upload an image to a NetCore Controller.
I'm receiving the IFileForm in the following controller:
[HttpPost("UpdateContactPhoto")]
public async Task<string> UpdateContactPhoto(IFormFile file){ //Forward the original IFileForm to another NetCore API. }
At this point everything is working correctly. IFileForm arrives perfect.
My problem is that I need to forward this IFileForm to another API (independent of this) whose input is an IFileForm with HttpClient PutAsync, but not works.
Can someone help me?
Thanks for help.
You can use this example. Note that the argument name is the same as the item added to the form-data:
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:57985");
byte[] data;
using (var br = new BinaryReader(file.OpenReadStream()))
{
data = br.ReadBytes((int) file.OpenReadStream().Length);
}
ByteArrayContent bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent();
multiContent.Add(bytes, "file", file.FileName);
var result = client.PutAsync("api/v1/FileManager", multiContent).Result;
if (result.StatusCode == HttpStatusCode.OK)
{
//do some things
}
}
You can also use this code to get the file from the HttpContext :
IFormFile file = HttpContext.Request.Form.Files[0];
Replace "Target url here" with your destination URL:
HttpClient httpClient = new HttpClient();
var streamcontent = new StreamContent(file.OpenReadStream());
var response = await httpClient.PutAsync("target url here", streamcontent);
Reference:
HttpClient PutAsync
StreamContent class
IFormFile interface

Elasticsearch NEST HttpClientHandler Certificate

I am trying to use Elasticsearch NEST with .NET Core and our Elasticsearch instance. We are connecting via SSL and it has a wildcard certificate which we need to accept programmatically. I am trying to figure out how to hook the HttpClientHandler to NEST to accept it. There doesn't appear to be good documentation on how, it just says to do it on their instructions https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/connecting.html#configuring-ssl.
I am looking for an example if possible. Thanks in advance!
I figured this out. I needed to create an HttpConnection and override the CreateHttpClientHandler method. Here is an example that returns true regardless of what the certificate is.
public class ConnectionWithCert : HttpConnection
{
protected override HttpClientHandler CreateHttpClientHandler(RequestData requestData)
{
var handler = base.CreateHttpClientHandler(requestData);
handler.ServerCertificateCustomValidationCallback = ValidateCertificate;
return handler;
}
private bool ValidateCertificate(HttpRequestMessage message, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
}
A person would want to check the cert to ensure that it is one they expect.
Then, I added this connection in the ConnectionSettings
var connectionSettings = new ConnectionSettings(connnectionPool, new ConnectionWithCert());
Probably want to do some Dependency Injection, but figured I would share the solution just in case anyone else wonders what they need to do.
This took me some head scratching to figure out, so I thought I would post it here. We are using a reverse proxy where we send the request to 443 SSL port (load balanced in azure to three client nodes) using a cert to authenticate, then forward that to the local client node to scatter to the data nodes. The cert is self signed, and is in the local store (Current User > Personal) on the server housing our api. The thumbprint is in our web.config.
public class ConnectionWithCert : Elasticsearch.Net.HttpConnection
{
protected override HttpWebRequest CreateHttpWebRequest(RequestData requestData)
{
var handler = base.CreateHttpWebRequest(requestData);
string certThumbprint = System.Configuration.ConfigurationManager.AppSettings["ElasticsearchCertificateThumbprint"];
X509Certificate2 certificate =
GetCertificateByThumbprint(certThumbprint);
handler.ClientCertificates.Add(certificate);
return handler;
}
/// <summary>
/// Get the certificate using the certificate thumbprint
/// </summary>
/// <param name="certificateThumbprint">Thumbprint of certificate</param>
/// <returns>Certificate object</returns>
public static X509Certificate2 GetCertificateByThumbprint(string certificateThumbprint)
{
Ensure.ArgumentNotEmpty(certificateThumbprint, nameof(certificateThumbprint));
// Open the certificate store
X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certificateStore.Open(OpenFlags.ReadOnly);
// Get the certificates
var matchingCertificates = certificateStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, false);
if (matchingCertificates.Count == 0)
{
// No certificate found
return null;
}
else
{
// Return first certificate
return matchingCertificates[0];
}
}
}
Once I have this, I can set that on my connectionSettings in my helper class:
public ElasticSearchHelper(string elasticSearchUrl, OcvElasticSearchDataProvider dataProvider, int elasticSearchConflictRetryCount)
{
// Parameters
this.elasticSearchConflictRetryCount = elasticSearchConflictRetryCount;
this.dataProvider = dataProvider;
// Create the ElasticSearch client and configure
var node = new Uri(elasticSearchUrl);
var pool = new SingleNodeConnectionPool(node);
var settings = new ConnectionSettings(pool, new ConnectionWithCert());
this.client = new ElasticClient(settings);
}
Now all operations carried out through my helper have the client cert attached, and is granted access through my reverse proxy.

How to pass and access ClaimsPrincipal Identity when using TestServer

I have an aspnetcore Web API and the host is configured for Windows Auth with the following code.
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls("http://TestServer:5000")
.UseStartup<Startup>()
.UseWebListener(options =>
{
options.Listener.AuthenticationManager.AuthenticationSchemes = Microsoft.Net.Http.Server.AuthenticationSchemes.NTLM;
//NOTE: Multiple auth assignments don't work in 1.0.0 - Wait for 1.1.* for the line below to start working
//See: https://github.com/aspnet/Announcements/issues/198
//options.Listener.AuthenticationManager.AllowAnonymous = true;
})
.Build();
host.Run();
The calling Client is configured with the following
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true,
PreAuthenticate = true
};
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri("http://TestServer:5000/");
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
In the service I'm calling I can access the calling user's ClaimsPrincipal Identity.
My Question is how do I call this service from an integration test using the TestServer Client initialised via the CreateClient method. What is the best way to ensure the Identity is set when calling from an integration test?
I'm also using XUnit in case you would like to know.
Any help or advice would be greatly appreciated.