C# SSL security certificate wizard needed!
Postman works, SoapUI works, my code gets a 404!
I have created a C# 4.7.2 MVC Web application. It makes a RestRequest().Post to a C# WebApi hosted on iis. This all works properly and as expected on http.
Now I want to make it https, I have added a security certificate to iis. Now I have problems.
If I make the request via Postman or SoapUI I get the response as before no problem. When I make the call with my C# HelperApp I get a 404 back! (Of course I checked the address path a thousand times.)
The 404 really confuses things. It's definitely there and we are hitting it successfully with Postman and SoapUI.
I suspect that I am not telling the RestClient to use SSL?
TIA.
case "Submit":
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3 | SecurityProtocolType.SystemDefault;
var apiServer = Startup.Config.ApiUrl; // this points to web.config <add key="API_URL" value="https://xxx.yyy.local:444/" />
//It works when I use <add key="API_URL" value="http://IEAVNBQ02:85/" />
var baseUrl = apiServer + $"api/ProcessQuotes/";
var client = new RestClient(baseUrl);
client.Options.MaxTimeout = -1;
var request = new RestRequest();
request.Method = Method.Post;
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", model.JsonRequest, ParameterType.RequestBody);
var response = client.Execute(request);
var Response is populated with:
<title>Network Error: 404</title>
<meta name="description" content="Proxy Exception: Network Error: 404" />
You may need to set
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
before making a call.
Just keep in mind this is a global setting.
Related
In my API project I have the following controller that works fine when called with Postman:
[HttpPost]
public async Task<ActionResult> Upload([FromHeader] string authorization, IFormFile payLoad) { ... }
When I use Postman, I add a string (Token) in the Auth section and specify the Type as Bearer Token. I then go to the Body section and set the Key payLoad as a File and choose a file to upload.
Postman generates C# - RestSharp code as follows:
var client = new RestClient("http://localhost:11764/api/logdata");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer exampleTokenString");
request.AddFile("payLoad", "/C:/path/oqwi.zip");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
I'm not using RestSharp so I haven't verified that the code above works but the Postman post itself within the tool works fine and my API gets all the data as I would expect.
In a separate client application, whenever I attempt to make a POST call, the API controller (at the top of this page) always receives null for the authorization parameter. The file loads in fine. Here is the client code trying to POST to the API with every example I attempted to add the header (I did not try them all at once):
Uri EndPoint = new Uri("http://localhost:11764/api/logdata");
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue("Bearer", AccessToken);
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + AccessToken);
var request = new HttpRequestMessage(HttpMethod.Post, EndPoint)
{
Content = fileAsFormData
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
request.Headers.Add("Authorization", "Bearer " + AccessToken);
var response = await client.SendAsync(request);
...
Note: The above is an example of every different attempt I made at adding the authorization and token; I didn't do all of the above at once.
I have inspected the object in the client during runtime and it appears as if the header(s) are added on where I would expect them to be. Using Fiddler, I can confirm this:
UPDATE: I've tried adding other headers, like CacheControl, and none of them make it through to the API. I see it on the client side during runtime, I see it in Fiddler, but then they're all scrubbed by the time they get to the API. I'm wondering if this Github discussion has anything to do with it:
https://github.com/dotnet/runtime/issues/26475
According to this Github ticket within the dotnet runtime team,
karelz commented on Oct 24, 2018 •
FYI: 2 weeks ago we released a security fix to remove Authorization request headers from redirects.
Thinking that redirects may be at the heart of the issue, by using Fiddler or the Visual Studio inspection tool, I was able to observe that the callback to my client was from https://localhost:5001. I was not expecting this...
When I was constructing the original client code to POST to my API, I was simply copying all of the values I had used when I was exercising these calls from Postman. As an example from Postman's C# RestSharp:
var client = new RestClient("http://localhost:11764/api/logdata");
This ultimately was a red herring caused by the intelligent way Postman handles redirects. Postman was indeed posting to http://localhost:11764 -- but then getting a secure redirect to https://localhost:5001. Postman would then subtly resend the original request with reattached headers to this new secure endpoint.
So after updating the endpoint that the client will POST to, from http://localhost:11764/api/logdata to https://localhost:5001/api/logdata, everything works as expected.
But why https://localhost:5001? It's setup this way in (most) launchSettings.json:
"MyProj.UploadApi.WebApi": {
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:11764"
}
My application is sending some data to some government's service.
The workflow is to first authenticate on their REST(JSON) service to get an authentication token, and then send the actual data+token to their SOAP service.
The problem is that if I call the authentication service in quick succession after the last soap request, their REST serice will return "404 – Not Found" HTML instead of JSON response.
This is the code for sending authentication requests:
RestClient client = new RestClient(ret.Url);
AuthRequestToken requestToken = new AuthRequestToken();
requestToken.userLoginDetails.organisationCode = _organizationCode;
requestToken.userLoginDetails.userId = _username;
requestToken.userLoginDetails.password = _password;
ret.RequestJson = requestToken.ToString();
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("cache-control", "no-cache");
request.AddParameter("application/json", ret.RequestJson, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
This is the code for sending SOAP requests:
HttpWebRequest webRequest = CreateWebRequest(envelope);
using (WebResponse webResponse = webRequest.GetResponse())
{
using (Stream responseStream = webResponse.GetResponseStream())
{
using (StreamReader rd = new StreamReader(responseStream))
{
ret.ResponseXML = rd.ReadToEnd();
}
responseStream.Close();
}
}
This is the CreateWebRequest() method
private HttpWebRequest CreateWebRequest(XElement content)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(_url);
//webRequest.Headers.Add("SOAPAction", action);
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
using (Stream stream = webRequest.GetRequestStream())
{
content.Save(stream);
}
return webRequest;
}
RestClient is a class in the RestSharp library downloaded from https://restsharp.dev/
Using TcpView or netstat -abn I can see that after any request (either RestClient or HttpWebRequest), the connection stays in ESTABLISHED state for up to 5-30 seconds.
Everything works fine 99% of the time, except in a specific scenario when I make a RestClient request within 5-30 seconds after the last HttpWebRequest, before the connection switches from ESTABLISHED to CLOSE_WAIT.
I should mention that this code was working perfectly up to a couple of days ago. Before then, their authentication service was on a different IP address form their SOAP service. Now they are on the same IPAddress, and probably even on the same physical server.
Before they switched the servers I used to call authentication request before each and every SOAP request, and it worked, but since this error started happening, I modified my code to authenticate only occasionally and use the same token for a bunch of SOAP requests. This considerably reduced the chance for this error, but I still ocassinaly get it when traffic is high.
It seems to me that RestClient and HttpWebRequest are using the same socket under the hood and one of them is not cleaning up properly. It seems that RestClient inherits some junk from the HttpWebRequest because the "404 - Not Found" returned by the service looks the same as when I deliberately navigate to the wrong URL of the authentication service.
It is also possible that I'm not disposing or closing something properly, but I tried closing every stream, client or connection I could find, and injected 'using' everywhere, but nothing seems to help.
I tried contacting the government's tech suport, but judging by my prior experience, it will take weeks before they even bother to connect me to someone who can understand the problem.
This is the 404 HTML I get:
<!doctype html>
<html lang="en">
<head>
<title>HTTP Status 404 – Not Found</title>
<style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style>
</head>
<body>
<h1>HTTP Status 404 – Not Found</h1>
<hr class="line" />
<p>
<b>Type</b> Status Report</p>
<p>
<b>Description</b> The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.</p>
<hr class="line" />
<h3>Apache Tomcat/9.0.35</h3>
</body>
</html>
Do you have any suggestion on what I could try to prevent this from happening?
As I said, I currently have some workaround which tris to refresh the token when it gets the chance, and even delay regular requests if necessary, but Id like to not use workarounds if possible, especially since I don't know what the socket timeout is. It is 5 sec on most computers, but on some wireless networks it stays ESTABLISHED for almost a minute.
If it matters, both services are on HTTPS.
Thank you!
I solved it by making a small console application which receives credentials through command line parameters, connects to the rest service and returns a token in the standard output.
Parent application periodically calls this exe in the background, and reads a new token from the standard output.
I have a service in my C# application which uses App Center's api to push notifications. All my request come back with 401s (Unauthorised) yet, when I used the same details on postman i.e. content, header auth, owner_name and app_name it works successfully and sends the application.
This is very confusing and I am wondering if Postman handles some extra bits and pieces which I am missing out.
C# Push Notification Service
private async Task<bool> PostHttpRequest(PushNotificationModel pushNotificationModel)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("X-API-Token", _appCenterApiToken);
var url = "https://api.appcenter.ms/v0.1/apps/myowner/myapp/push/notifications";
var content = JsonConvert.SerializeObject(pushNotificationModel);
var buffer = Encoding.UTF8.GetBytes(content);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.PostAsync(url, byteContent);
return response.IsSuccessStatusCode;
}
I debugged this code and used the same data being passed to the client to use on Postman, hence why Model data is absent for example.
Help is much appreciated!
Put the code below in your startup form.
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
I figured out the issue using Telerik Fiddler. This helpful tool allows you to actively view HTTP traffic to and from your machine. Now the mistake was a silly one on my part but it boiled down to changing this:
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("X-API-Token", _appCenterApiToken);
to:
_httpClient.DefaultRequestHeaders.Add("X-API-Token", _appCenterApiToken);
As the first one was actually appearing in the HTTP header as Authorization : X-API-Token apiToken rather than X-API-Token : apiToken it caused the request to be bad, hence returning 401 (Unauthorised)
I've created my app in Yelp, got my api key, and things work fine from Postman when executing a business search.
However, when testing from c#, I receive a 401 unauthorized error with a TOKEN_MISSING error that says ""{\"error\": {\"code\": \"TOKEN_MISSING\", \"description\": \"An access token must be supplied in order to use this endpoint.\"}}"".
I'm supplying my api key correctly though, and the Yelp documentation says that's all I need, so I'm not sure what the problem is. Here are 2 separate c# code samples that do NOT work (I've replaced my actual api key with for security concerns):
Example using WebRequest:
var webRequest = WebRequest.Create("http://api.yelp.com/v3/businesses/search?term=Clayton+Bicycle+Center&location=5411+Clayton+Rd%2c+Clayton%2c+CA+94517%2c+US");
webRequest.Method = "GET";
webRequest.Headers.Add("Cache-Control", "no-cache");
webRequest.Headers.Add("Authorization", "Bearer <my_api_key>");
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
var stream = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8);
var content = stream.ReadToEnd();
Console.Write(content);
Example using RestSharp:
var client = new RestClient("http://api.yelp.com/v3/businesses/search?term=Clayton+Bicycle+Center&location=5411+Clayton+Rd%2c+Clayton%2c+CA+94517%2c+US");
var request = new RestRequest(Method.GET);
request.AddHeader("Cache-Control", "no-cache");
request.AddHeader("Authorization", "Bearer <my_api_key>");
var response = client.Execute(request);
Console.Write(response.Content);
I've examined the requests in Fiddler, and both are sending the same headers as the working Postman search, but both return 401 unauthorized error while Postman returns the search results. Any ideas?
Edit:
Well this is embarrassing, apparently my issue was I was attempting to access the Yelp API via http instead of https. Once I changed to https, everything worked as expected.
Changed endpoint to use https instead of http, works now.
I'm trying to test an API call on my local machine, using RestSharp, with the following code...
var client = new RestClient("https://[API URL]");
var request = new RestRequest( Method.POST);
request.AddParameter("session", this, ParameterType.RequestBody);
IRestResponse<SessionOut> response = client.Execute<SessionOut>(request);
return response.Data.session.id;
In response I get the an error telling me that the request was aborted because it "could not create SSL/TLS secure channel".
Does this mean I need to try and set up https://localhost instead of http://localhost in order to call APIs at https:// addresses?
UPDATE
I have updated my code to the following, as per #Shai_Aharoni's answer below. I am still getting the same error however.
string pathToYourClientCert = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "[my certificate file");
var client = new RestClient("[API URL]/");
client.ClientCertificates = new X509CertificateCollection();
client.ClientCertificates.Add(new X509Certificate(pathToYourClientCert));
var request = new RestRequest( Method.POST);
request.AddParameter("session", this, ParameterType.RequestBody);
IRestResponse<SessionOut> response2 = client.Execute<SessionOut>(request);
Try adding this to your code:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Related resources:
Some useful information about setting TLS 1.2 in another stackoverflow post
Well... There are a few steps that you need to complete before you can call you HTTPS endpoint.
1) Make sure that your server supports an HTTPS endpoint (i.e : that the URL https://[APIURL] is reachable.
2) Have a valid server (the api server) certificate installed on the machine that executes the HTTPS call.
3) Add the certificate to your RestSharp client. Similar to something like this:
string pathToYourClientCert = "cer/cert.cer";
client.ClientCertificates.Add(new X509Certificate(pathToYourClientCert));
Hope this helps...
Follow these steps:
https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/http-stack?tabs=macos
Also add this to your code in MainActivity.cs -> OnCreate -> Before you load your app:
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
I had to do both for my API requests to work in Xamarin Forms!
Make sure your CERTIFICATE is not SELF-SIGNED!!!