I have created two Web API in Asp.net Core one is Wrapper Service to be deployed on DMZ Server and other is core service which have access to our DB Non DMZ. The problem i am facing is that i am unable to communicate between two services.
Both are running on local machine through dockers. When i try to hit core service running on url: https://localhost:56788/Rewards thorugh HttpClient/WebRequest i got message
No connection could be made because the target machine actively refused it.
DMZ Controller Logic
[Route("[controller]")]
[ApiController]
public class RewardsController : ControllerBase
{
[HttpGet]
public string Get()
{
string response = string.Empty;
//using (var client = new HttpClient())
//{
// client.BaseAddress = new Uri("http://localhost:5000/");
// //HTTP GET
// var responseTask = client.GetAsync("Rewards");
// responseTask.Wait();
// var result = responseTask.Result;
// if (result.IsSuccessStatusCode)
// {
// response = result.ToString();
// }
// else //web api sent error response
// {
// //log response status here..
// response = "Error";
// }
//}
string sURL = "https://localhost:56788/Rewards";
WebRequest wrPostURL = WebRequest.Create(sURL);
wrPostURL.Method = "GET";
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (HttpWebResponse webresponse = wrPostURL.GetResponse() as HttpWebResponse)
{
Encoding enc = System.Text.Encoding.GetEncoding("utf-8");
StreamReader loResponseStream = new StreamReader(webresponse.GetResponseStream(), enc);
var jsonResponse = loResponseStream.ReadToEnd();
loResponseStream.Close();
webresponse.Close();
}
return response;
}
}
NON-DMZ Controller Logic
[Route("[controller]")]
[ApiController]
public class RewardsController : ControllerBase
{
[HttpGet]
public string Get()
{
return "Hello";
}
}
Update
This problem is due to dockers. When i deploy both APIs to IIS then it will work fine but i have to do it with Dockers
The problem occurs due to the fact that i was calling localhost to communicate with other API running on another container. When we call localhost the first container start looking for the service that is running on same container. By using VM/Local Machine IP instead of localhost the problem can be avoided.
Related
I am trying to implement the certificate authentication in .net core API(Server/target) and this API will be invoked in to another API(Client) .Here is the piece of code of client api which makes request to server/target api.But I'm facing an error on the server/target api .I'm running these two services from local and both certificates have already installed
Client side controller logic
[HttpGet]
public async Task<List<WeatherForecast>> Get()
{
List<WeatherForecast> weatherForecastList = new List<WeatherForecast>();
X509Certificate2 clientCert = Authentication.GetClientCertificate();
if (clientCert == null)
{
HttpActionContext actionContext = null;
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Client Certificate Required"
};
}
HttpClientHandler requestHandler = new HttpClientHandler();
requestHandler.ClientCertificates.Add(clientCert);
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri("https://localhost:11111/ServerAPI")
};
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/xml"));//ACCEPT head
using (var httpClient = new HttpClient())
{
//httpClient.DefaultRequestHeaders.Accept.Clear();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44386/ServerAPI"),
Method = HttpMethod.Get,
};
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT head
//using (var response = await httpClient.GetAsync("https://localhost:11111/ServerAPI"))
using (var response = await httpClient.SendAsync(request))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResposne = await response.Content.ReadAsStringAsync();
weatherForecastList = JsonConvert.DeserializeObject<List<WeatherForecast>>(apiResposne);
}
}
}
return weatherForecastList;
}
authentication class
public static X509Certificate2 GetClientCertificate()
{
X509Store userCaStore = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
try
{
string str_API_Cert_Thumbprint = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindByThumbprint, str_API_Cert_Thumbprint, false);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
if(System.DateTime.Today >= System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()))
{
throw new Exception("Certificate has already been expired.");
}
else if (System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()).AddDays(-30) <= System.DateTime.Today)
{
throw new Exception("Certificate is about to expire in 30 days.");
}
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch (Exception ex)
{
throw;
}
finally
{
userCaStore.Close();
}
}
Server/target api code
[HttpGet]
public IEnumerable<WeatherForecast> Getcertdata()
{
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
//return new List<WeatherForecast>();
}
You have much more problems here, the code is significantly flawed and insecure in various ways. Let's explain each issue:
HttpClient in using clause in client side controller logic
Although you expect to wrap anything that implements IDisposable in using statement. However, it is not really the case with HttpClient. Connections are not closed immediately. And with every request to client controller action, a new connection is established to remote endpoint, while previous connections sit in TIME_WAIT state. Under certain constant load, your HttpClient will exhaust TCP port pool (which is limited) and any new attempt to create a new connection will throw an exception. Here are more details on this problem: You're using HttpClient wrong and it is destabilizing your software
Microsoft recommendation is to re-use existing connections. One way to do this is to Use IHttpClientFactory to implement resilient HTTP requests. Microsoft article talks a bit about this problem:
Though this class implements IDisposable, declaring and instantiating
it within a using statement is not preferred because when the
HttpClient object gets disposed of, the underlying socket is not
immediately released, which can lead to a socket exhaustion problem.
BTW, you have created a client variable, but do not use it in any way.
Ignore certificate validation problems
The line:
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
make you vulnerable to MITM attack.
you are doing client certificate authentication wrong
The line:
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
It is not the proper way how to do client cert authentication. What you literally doing is passing certificate's public part to server. That's all. You do not prove private key possession which is required to authenticate you. The proper way to do so is:
requestHandler.ClientCertificates.Add(clientCert);
This will force client and server to perform proper client authentication and check if you possess the private key for certificate you pass (it is done in TLS handshake automatically). If you have ASP.NET on server side, then you read it this way (in controller action):
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
Non-standard cert store
In authentication class you open StoreName.TrustedPeople store, while normally it should be StoreName.My. TrustedPeople isn't designed to store certs with private key. It isn't a functional problem, but it is bad practice.
unnecessary try/catch clause in authentication class
If you purposely throw exceptions in method, do not use try/catch. In your case you simply rethrow exception, thus you are doing a double work. And this:
throw new Exception("Certificate is about to expire in 30 days.");
is behind me. Throwing exception on technically valid certificate? Really?
server side code
As said, all this:
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
must be replaced with:
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
BTW:
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
This is another disaster in your code. You are loading the server certificate from PFX just to compare their thumbprints? So, you suppose that client will have a copy of server certificate? Client and server certificates must not be the same. Next thing is you are generating a lot of copies of server certificate's private key files. More private key files you generate, the slower the process is and you just generate a lot of garbage. More details on this you can find in my blog post: Handling X509KeyStorageFlags in applications
I am new to Bazor web assembly (Blazor Client).
I have created a project with Asp.net Core web api with Angular Application.
In order to work with asp.net core web api and angular,
I can use the default functionality like
AddSpaStaticFiles
UseSpa
How can I use Blazor webassembly like the angular?
Or
How can replace the existing Angular SPA with Blazor Client?
Some links provided a solution for Blazor assembly preview.
But the same functionality not found on the latest.
https://csharp.christiannagel.com/2019/08/27/blazorserverandclient/
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
Remember that Web Assembly Apps are created to work like another SPA like Angular or React, it means that you create your view presentation or Blazor Web Assembly App in a independent project, then you get the data from some Web Service, for example an Rest API made in .Net Core 3.1, to create the Blazor Web Assembly Project you just go to File -> New -> Project -> Blazor App -> Blazor WebAssembly App, do not choose ASP.NET Core Hosted option, this will attach your project to the .net core backend directly like an MVC Project.
After having your new project created, you just simple need to call your backend end-points with the Built-In Http Client library that comes with .Net Core or you can create your own library using .Net HttpClient and Inject it in your components or pages using Dependency Injection, if you want to create your own library, follow this process:
First Create this HttpObject:
public class HttpResultObject<T>
{
public bool IsSuccessful { get; set; }
public HttpStatusCode HttpResultCode { get; set; }
public T Result { get; set; }
}
Then create your Library Class:
public class MyLibrary : IMyLibrary
{
public string GetApiUri(string server)
{
if (server.Equals("auth"))
return "https://localhost:8080/api/";
return "https://localhost:8080/api/";
}
//Http Get Method Example
public async Task<HttpResultObject<U>> SetAppMethodGetParametersAsync<U>(string server, string method, Dictionary<string, object> parameters, CancellationToken token) where U : class
{
string getParameters = string.Empty;
foreach(var p in parameters)
{
if (p.Value.GetType() == typeof(string))
getParameters = getParameters.Equals(string.Empty) ? "?" + p.Value.ToString() : "&" + p.Value.ToString();
}
var uri = new System.Uri(GetApiUri(server) + method + "/" + getParameters) ;
var response = await CallAppMethodGetAsync(uri, token);
var result = new HttpResultObject<U>()
{
IsSuccessful = response.IsSuccessStatusCode,
HttpResultCode = response.StatusCode,
Result = JsonSerializer.Deserialize<U>(response?.Content?.ReadAsStringAsync()?.Result)
};
return result;
}
private async Task<HttpResponseMessage> CallAppMethodGetAsync(System.Uri uri, CancellationToken token)
{
Console.WriteLine(uri.ToString());
HttpStatusCode responseStatus = HttpStatusCode.BadRequest;
try
{
HttpClient client = new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(240000)
};
HttpResponseMessage response = new HttpResponseMessage(responseStatus);
HttpRequestMessage request = new HttpRequestMessage()
{
RequestUri = uri,
Method = HttpMethod.Get
};
var authToken = this.GetLocalStorageItem("authToken");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (authToken != null && authToken.GetType() == typeof(string))
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Convert.ToString(authToken));
token.ThrowIfCancellationRequested();
response = await client.SendAsync(request, token);
responseStatus = response == null ? HttpStatusCode.BadRequest : response.StatusCode;
if (response != null && responseStatus != HttpStatusCode.OK && responseStatus != HttpStatusCode.Accepted)
{
HttpResponseMessage result = new HttpResponseMessage(responseStatus)
{
Content = new StringContent(response.Content?.ReadAsStringAsync()?.Result, Encoding.UTF8, "application/json")
};
return response;
}
return response;
}
catch (WebException webException)
{
}
catch (System.Net.Sockets.SocketException socketException)
{
}
catch (HttpRequestException httpRequestException)
{
}
catch (ArgumentException argumentException)
{
}
catch (System.Exception exception)
{
}
return new HttpResponseMessage(responseStatus);
}
}
Now create your ILibrary Interface and declare the Implemented Methods:
public interface IMyLibrary
{
string GetApiUri(string server);
Task<HttpResultObject<U>> SetAppMethodGetParametersAsync<U>(string server, string method, Dictionary<string, object> parameters, CancellationToken token) where U : class;
}
Declare your Dependency Injection in your startup.cs in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyLibrary, MyLibrary>();
}
Now, if you want to you use your library in some Razor Component or Page just inject it like this:
#inject IMyLibrary _myLibrary
#code
{
private async Task MyHttpGetCall()
{
var cts = new CancellationTokenSource();
var result = await _myLibrary.SetAppMethodPostParametersAsync<HttpResultObject<MyCustomObject>>("auth", new Dictionary<string, object>(), cts.Token);
if (result.IsSuccesful)
{
//whatever you want to do
}
}
}
And that is all!, those are the 2 ways to connect your front-end web site developed with Blazor Web Assembly App to your Backend the same way you does with Angular or React.
I am trying to create microservices using Spring-boot Java and SteelToe ASP.NET
Step-1: I created a full service using Java (A service with UI and API. It is hosted on PCF). The API has ClassesControler defined inside.
Step-2: Create a microservice using ASP.NET, SteelToe. Register the service in Eureka and make it discoverable using Zuul.
Step-3: Use the Interface, Service approach to access the JAVA microservice(s)
namespace employee-client.Service
{
public interface IRelayService
{
Task<HttpResponseMessage> getClassesList(string relativeUrl = "/api/v1/classes");
}
}
Service with Implementation for Interface:
namespace employee-client.Service
{
public class RelayService : IRelayService
{
DiscoveryHttpClientHandler _handler;
string _accessToken;
private const string BASE_URL = "https://www.example.com";
public QortaService(IDiscoveryClient client, string accessToken)
{
_handler = new DiscoveryHttpClientHandler(client);
_accessToken = accessToken;
}
public async Task<HttpResponseMessage> getClassesList(string relativeUrl)
{
string classesUrl= BASE_URL + relativeUrl;
HttpClient client = GetClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(classesUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
}
private HttpClient GetClient()
{
var client = new HttpClient(_handler, false);
return client;
}
}
}
I came up with this approach based on the example in SteelToe but I hate hardcoding the BASE_URL.
Question: I very much like the #FeignClient annotation approach used in Java. Any ideas about how I can access an existing microservice in a better way. If so, an example would be much appreciated
Edit:
I modified the question to make more clear.
The flow of traffic is from Java Service to .NET service. .NET service requests for a list of classes from the controller in JAVA service (ClassesController.java)
I'm unclear which direction traffic is flowing in your scenario, but I think you're saying the .NET application is trying to call the Java application. The code you're using is from before HttpClientFactory was introduced and is a bit clunkier than what's possible now in general. Steeltoe can be used with HttpClientFactory for a better overall experience.
Steeltoe has debug logging available to confirm the results of service lookup if you set logging:loglevel:Steeltoe.Common.Discovery = true in your application config.
You didn't mention specifically what isn't working, but I'm guessing you're getting a 404 since it looks like your code will create a request path looking like https://fortuneService/api/fortunes/random/api/v1/classes
If you're looking for something like Feign in .NET, you could try out DHaven.Faux
For others who are looking for the same:
namespace employee-client.Service
{
public class RelayService : IRelayService
{
private const string CLASSES_API_SERVICEID = "classes-api";
IDiscoveryClient _discoveryClient;
DiscoveryHttpClientHandler _handler;
string _accessToken;
public RelayService(IDiscoveryClient discoveryClient, string accessToken)
{
_discoveryClient = discoveryClient;
_handler = new DiscoveryHttpClientHandler(client);
_accessToken = accessToken;
}
public async Task<HttpResponseMessage> getClassesList()
{
var classesApiInstances = _discoveryClient.GetInstances(CLASSES_API_SERVICEID);
Uri classesApiUri = classesApiInstances[0].Uri;
string classesUrl= classesApiUri.AbsoluteUri + relativeUrl;
HttpClient httpClient = GetClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(classesUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead);
}
private HttpClient GetClient()
{
var client = new HttpClient(_handler, false);
return client;
}
}
}
I have been trying to update my API call using the suggestion from here
to only have 1 instance of HttpClient. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/#
This works fine and I have my do not use up all of the ports, however when I try to create a HttpClientHandler to pass along the default credentials my ports start being used up again. My API is setup to use Windows Auth for security reasons so I need to pass along the app pools credentials for a successful call.
Here are the 2 code blocks
public static class WebApiCallUtility
{
private static HttpClientHandler _handlerNoCred = new HttpClientHandler();
private static HttpClient _clientNoCred = new HttpClient(_handlerNoCred);
private static HttpClientHandler _handlerCred = new HttpClientHandler { UseDefaultCredentials = true };
private static HttpClient _clientCred = new HttpClient(_handlerCred);
//Working ports are not used up
public static HttpResponseMessage SendHttpGetRequestNoCred(string webApiUrl, string logSourceName, string subId)
{
_clientNoCred.DefaultRequestHeaders.Accept.Clear();
_clientNoCred.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage result = _clientNoCred.GetAsync(webApiUrl).Result;
return result;
}
//No working tons of ports open hanging out with TIME_WAIT status
public static HttpResponseMessage SendHttpGetRequestCred(string webApiUrl, string logSourceName, string subId)
{
_clientCred.DefaultRequestHeaders.Accept.Clear();
_clientCred.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage result = _clientCred.GetAsync(webApiUrl).Result;
return result;
}
}
Any help would be appreciated.
Thanks
I was able to revolve this issue buy using a different handler type
private static WebRequestHandler _handlerCred = new WebRequestHandler
{
UseDefaultCredentials = true,
UnsafeAuthenticatedConnectionSharing = true
};
private static HttpClient _clientCred = new HttpClient(_handlerCred);
I found this answer here Static HttpClient still creating TIME_WAIT tcp ports
I have been stuck all day on a stupid problem with registering a user to my application.
Here is my code once the 'Register' button is clicked:
public ICommand RegisterCommand
{
get
{
return new Command(async() =>
{
var isSuccess = await _apiServices.RegisterAsync(Email, Password, ConfirmPassword);
if (isSuccess){
Message = "Registered Successfully";
}
else
{
Message = "Retry later";
}
});
}
}
Api services Register Async method:
public async Task<bool> RegisterAsync(string email, string password, string confirmPassword)
{
try
{
System.Diagnostics.Debug.WriteLine("Email: "+email);
var client = new HttpClient();
var model = new RegisterBindingModel
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword
};
var json = JsonConvert.SerializeObject(model);
HttpContent content = new StringContent(json);
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:63724/api/Account/Register", content);
if (response.IsSuccessStatusCode)
{
return true;
}
return false;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Error: "+e);
throw;
}
}
}
The Error that I get is:
System.Net.Http.HttpRequestException: An error occurred while sending the request ---> System.Net.WebException: Error: ConnectFailure (Connection refused) ---> System.Net.Sockets.SocketException: Connection refused
at System.Net.Sockets.Socket.Connect (System.Net.EndPoint remoteEP) [0x000b6] in <6c708cf596db438ebfc6b7e012659eee>:0
at System.Net.WebConnection.Connect (System.Net.HttpWebRequest request) [0x0016d] in <6c708cf596db438ebfc6b7e012659eee>:0
--- End of inner exception stack trace ---
To me this is very frustrating as I can register a use using Postman with the exact same localhost address. I am following Houssem Dellai's Xamarin.Forms mvc web api tutorials which can be found here
I had an issue with httpclient during the development of my app. I believe there was an issue with the cross-platform implementation of the httpclient class. iOS didn't know how to handle it.
Instead I implemented a very simple httpclient library called flurl: http://tmenier.github.io/Flurl/
First, you will need to install flurl in all project directories (iOS, Android, and the PCL) then the implementation is very simple.
using Flurl;
using Flurl.Http;
public async Task<User> CreateUserAsync(RegisterUserModel userModel)
{
string url = "your/backend/here";
//resp is a user object received and automatically converted into a c# object through the use of .ReceiveJson<typeofobject>();
var resp = await (url).PostJsonAsync(userModel)
.ReceiveJson<User>();
if (resp.LoginSession != null)
{
//Raise my registered event to let other classes know to proceed
OnUserRegistered(resp);
}
return resp;
}
As you can see it makes httpclient implementation very simple. Hopefully this helps.