cURL and .Net (c#) API Token - c#

I've read and re-read stackoverflow and google searches in general and I just can't seem to find a solution to my issue. I'm positive it is my ignorance.
I am trying to reproduce a cUrl call in .Net (c# specifically) and am having a devil of a time wading through and figuring out why it isn't working. this cUrl call uses a token and the username is not required when I do so.
The following cUrl call works as expected when called from the command line. The API token has been changed to protect the innocent.
curl -u x:8cb60a319c71be3356da2ea6d7c7650b -H "Content-Type: application/json" https://deskapi.gotoassist.com/v1/incidents.json
I have tried the following:
WebRequestHandler webHandler = new WebRequestHandler();
webHandler.Credentials = new System.Net.NetworkCredential("x", "8cb60a319c71be3356da2ea6d7c7650b");
HttpClient client = new HttpClient(webHandler);
client.BaseAddress = new Uri("https://deskapi.gotoassist.com/v1/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("incidents.json");
if (response.IsSuccessStatusCode)
{
Console.Write("Success!");
}
The response gives a "Bad Request". The actual requestmessage is:
response.RequestMessage {Method: GET, RequestUri: 'https://deskapi.gotoassist.com/v1/incidents.json', Version: 1.1, Content: , Headers:
{
Accept: application/json
}}
If that helps any.
Based on other stackoverflow examples I also tried:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://deskapi.gotoassist.com/v1/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("8cb60a319c71be3356da2ea6d7c7650b");
HttpResponseMessage response = await client.GetAsync("incidents.json");
if (response.IsSuccessStatusCode)
{
Console.Write("Success!");
}
Console.Write(response.RequestMessage.ToString());
With another "Bad Request". The message is similar:
response.RequestMessage {Method: GET, RequestUri: 'https://deskapi.gotoassist.com/v1/incidents.json', Version: 1.1, Content: , Headers:
{
Accept: application/json
Authorization: 8cb60a319c71be3356da2ea6d7c7650b
}} System.Net.Http.HttpRequestMessage
The actual uri looks correct to me. The content type looks correct to me. The only thing I can't find a definitive example to do is send that "x:8cb60a319c71be3356da2ea6d7c7650b" other than the ways I have tried it.
Any thoughts? This is my first time trying to do anything like this so hopefully I'm just doing some ignorant something-something. I've tried to follow example after example but nothing gets me an successful call. Thank you in advance for helping.

The cUrl command uses the Content-Type header which is only intended for when you are sending a body e.g. with PUT or POST. It is not the same header as Accept. Accept is the correct header to send with a GET request.
When I try hitting the server without any auth header, I get back a 400 with the message "Unknown OAuth signature method". The response for missing auth should really be a 401 and a www-authenticate header that tells us what kind of scheme to use.
Assuming that the server is actually looking for a OAuth2 bearer token, then you might want to try,
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer","8cb60a319c71be3356da2ea6d7c7650b");

Debug your code using Fiddler
By using Fiddler you will exactly know what is happenning during these calls,
Run it against CURL command line
Run it against your code
Basically you have nothing to do, just install it, run it and do some Internet activity. You will then see what's being intercepted.
To fix your problem, look at the Inspectors/Raw tab and compare them.
Here's an example of an issue I've had and what I found out that was missing with Fiddler :
Equivalent of "curl -F" parameter for System.Net.Http.MultipartFormDataContent?

Related

Replicate POSTMAN GET request in C#/VB.net with Authorization

I've been here for 2 days now driving me nuts.
All I want to do it call a webservice at:
https://use-land-property-data.service.gov.uk/api/v1/datasets
Which returns some JSON object.
It requires the "Authorization" header to be set with an API Key that I have.
I've tried it in POSTMAN and it works.
However trying to get a Webclient or Httpclient version working is currently beyond me. I've tried countless examples here on SO. None return the same responses as POSTMAN. All return "Request Rejected"
e.g.
Using client = New HttpClient()
client.DefaultRequestHeaders.Add("Authorization", "MYKEY")
Dim response = Await client.GetStringAsync("https://use-land-property-data.service.gov.uk/api/v1/datasets")
Return response
End Using
what is the equivalent in httpclient to replicate the postman Authorization header?
Try:
httpClient.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Bearer", "Your Key")

Receiving 401 "Unauthorized" error while using Google Cloud AutoML via HttpClient

I am writing a WPF application with C# that attempts to make an Google Cloud AutoML API call with HttpClient. I am able to make contact with the server but always get back an "Unauthorized" response. I have scoured StackOverflow and the AutoML documentation for any hint as to how to properly turn the "CURL" request into a simple HTTP request that I can execute programmatically within my C# application, but haven't found anything that gave enough guidance up to this point (hence my question).
Here is the CURL request that I am modeling my HTTP request after:
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
https://automl.googleapis.com/v1beta1/projects/image-object-detection/locations/us-central1/models/MyProjectId:predict -d #request.json
There are elements of this request that I cannot figure out how to translate into C#, namely the Authorization: Bearer component. Do I need to somehow find a token and add it to a header or something? If so, how do I acquire this token in string form? That seems to be what I'm really stuck on.
And here is the C# code that I actually have up to this point.
public async Task<object> GetPrediction(string imagePath)
{
string apiKey = "MyApiKey";
string projectId = "MyProjectId";
HttpResponseMessage response;
byte[] img = File.ReadAllBytes(imagePath);
string jsonBody = "{\"payload\":{\"image\":{\"imageBytes\":\"" + Encoding.UTF8.GetString(imgBody) + "\"}}}";
string uri = $"https://automl.googleapis.com/v1beta1/projects/image-object-detection/locations/us-central1/models/{projectId}:predict?key={apiKey}";
string token = “MyToken”;
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
request.Headers.Authorization = new AuthenticarionHeaderValue(“Bearer”, token);
request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
response = await client.SendAsync(request);
return Task.FromResult(response);
}
This code basically makes contact, then I get back a 401 "unauthorized" status code. Any suggestions or guidance would be greatly appreciated, and if additional information is required, I would be glad to post more. Thanks!
Update:
I modified the code block to include the suggested change from Nkosi, but I am still seeing the same 401 status code.
I am not seeing the Authorization header added to request
-H "Authorization: Bearer $(gcloud auth application-default print-access-token)"
like in the cURL example
set the Authorization on the request before sending it
//...
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "{token-here}");
//...

Unsupported Media Type with HttpClient / .NET Core

I am working with a RESTful API that supports POST requests in JSON format. The API's own Swagger documentation shows that this is a valid call to one of its endpoints:
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '<JSON>' '<URL>'
Where <JSON> and <URL> are the valid JSON message and the endpoint's URL. From this Swagger documentation I gather that any posts to this endpoint must include both a Content-Type and Accept headers set to application/json.
I am writing a C# method that will use .NET Core's HttpClient class to post to this endpoint. However, upon posting my message I receive an HTTP 415 error code back, for Unsupported Media Type. From what I've learned so far, the Content-Type header must be set in your content (I am using the StringContent class) and the Accept header can only be set in the HttpClient headers. Here is my particular example:
var httpContent = new StringContent("<JSON>", Encoding.UTF32, "application/json");
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var responseMessage = httpClient.PostAsync("<URL>", httpContent);
var result = responseMessage.Result;
Once again, where <JSON> and <URL> are the valid JSON message and the endpoints's URL. It would seem to me that the third line, on which I reference httpCllient.DefaultRequestHeaders, is not adding the Accept: application/json header to my request. If I manually add the header to the httpContent.Headers collection, I get a run-time error that tells me that Accept is not a header I can add to the httpContent. That's why I am hoping to add it to the httpClient instead.
I have validated the URL and my JSON with Swagger, so I know those are correct. Also, the request is done over HTTPS, so I can't use Fiddler to validate that the Accept header is being included. And while I could enable decryption in Fiddler, that's a whole other ball of wax. I don't want to add their root certificate to my system, especially if I'm missing something fairly simple, which this seems to be.
Any pointers will be appreciated. Thanks.
what about if you try:
var httpContent = new StringContent(jsonData, Encoding.UTF8, "application/json");
You shouldn't need to add an Accept header.

How do I not exclude charset in Content-Type when using HttpClient?

I am attempting to use HttpClient in a .net core project to make a GET request to a REST service that accepts/returns JSON. I don't control the external service.
No matter how I try, I can't figure out to set the Content-Type header to application/json only.
When I use
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
it sends in the HTTP GET request:
Content-Type: application/json; charset=utf-8
However, this particular service does not work with this. It will only work if the header is:
Content-Type: application/json
I've tried setting headers without validation, and all the approaches I've found on the web/SO doesn't apply to .net core. All the other the approaches to sending HTTP requests aren't available in .net core, so I need to figure this out. How can I exclude the charset in content-type?
EDIT with workaround
As mentioned in the answers, the service should be using the Accept header. The workaround (as Shaun Luttin has in his answer) is to add an empty content to the GET (what? GETs don't have content! yeah...). It's not pretty, but it does work.
You're setting the Accept header. You need to set the ContentType header instead, which is only canonical for a POST.
var client = new HttpClient();
var content = new StringContent("myJson");
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var result = client.PostAsync("http://bigfont.ca", content).Result;
If you really want to set it for a GET, you can do this:
var client = new HttpClient();
var message = new HttpRequestMessage(HttpMethod.Get, "http://www.bigfont.ca");
message.Content = new StringContent(string.Empty);
message.Content.Headers.Clear();
message.Content.Headers.Add("Content-Type", "application/json");
var result = client.SendAsync(message).Result;
If you are the client and you perform a GET request how can you specify the Content-Type? Isn't it supposed to say what your are able to Accept ? According to this 7.2.1 Type you can only set Content-Type when there is Body.

HttpClient.SendAsync not sending request body

I am using the ASP.NET Web API Client Libraries for .NET 4.0 (Microsoft.AspNet.WebApi.Client version 4.0.30506.0).
I need to send an HTTP DELETE with a request body. I have coded it as follows:
using (var client = new HttpClient())
{
client.BaseAddress = Uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// I would normally use httpClient.DeleteAsync but I can't because I need to set content on the request.
// For this reason I use httpClient.SendAsync where I can both specify the HTTP DELETE with a request body.
var request = new HttpRequestMessage(HttpMethod.Delete, string.Format("myresource/{0}", sessionId))
{
var data = new Dictionary<string, object> {{"some-key", "some-value"}};
Content = new ObjectContent<IDictionary<string, object>>(data, new JsonMediaTypeFormatter())
};
var response = await client.SendAsync(request);
// code elided
}
Per Fiddler, the request body is never serialized:
DELETE http://localhost:8888/myApp/sessions/blabla123 HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: localhost:8888
Content-Length: 38
Expect: 100-continue
The response from the server:
HTTP/1.1 408 Request body incomplete
Date: Sun, 10 Aug 2014 17:55:17 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-cache, must-revalidate
Timestamp: 13:55:17.256
The request body did not contain the specified number of bytes. Got 0, expected 38
I have tried a number of workarounds, including changing the type being serialized to something else, doing the serialization myself with JsonSerialize, changing the HTTP DELETE to PUT, etc...
Nothing worked. Any help would be much appreciated.
I resolved the issue, though it does not make sense. I noticed that if I changed my call to HTTP PUT or POST, it still failed to serialize the Content as a request body. That was rather strange as previous PUTs and POSTs were successful. After doing a ton of debugging into framework libraries (using Reflector), I finally got to the only thing left that was "different."
I am using NUnit 2.6.2. The structure of my test is:
[Test]
async public void Test()
{
// successful HTTP POST and PUT calls here
// successful HTTP DELETE with request body here (after
// moving it from the TearDown below)
}
[TearDown]
async public void TerminateSession()
{
// failed HTTP DELETE with request body here
}
Why does this fail in the TearDown but not in the Test itself? I have no idea. Is something going on with the TearDown attribute or with the use of the async keyword (since I await async calls)?
I am not sure what it is causing this behavior, but I do know now that I can submit an HTTP DELETE with a request body (as outlined in my code sample in the question).
Another solution that worked is as follows:
[Test]
async public void Test()
{
// create and use an HttpClient here, doing POSTs, PUTs, and GETs
}
// Notice the removal of the async keyword since now using Wait() in method body
[TearDown]
public void TerminateSession()
{
// create and use an HttpClient here and use Wait().
httpClient.SendAsync(httpRequestMessage).Wait();
}
I know it's never quite that helpful to say, "don't do it that way", but in this case I think it makes sense to split the calls into a DELETE followed or preceeded by a POST or PUT.
The HTTP RFC doesn't explicitly opine on the matter, so technically it means that we can. The other question, however, is should we do it.
In cases such as this I would look for other implementations to see what is the de facto standard. As you've found in the .net implementation, it appears that the designers did not expect to send a body with the DELETE call. So, let's look at another popular (and very different impl) Python Requests:
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
No body here other. So, if the spec authors didn't mention it, and popular implementations assume that there's no body, then the principle of least surprise means we shouldn't do it either.
So, if you can change the API, it will be easier on clients of the API to split into two calls. Otherwise, you'll likely have to resort to custom hackery to cram the body into a DELETE call.
The good news is that you've likely found a bug in the .net framework, which is an achievement in and of itself. Clients advertising a non-zero Content-Length without actually sending it are broken.
In case anybody else runs into this, one thing I've noticed that can cause this is if you set a header with a newline in it.
We had an encrypted OAuth token, which gets decrypted at runtime and set as the OAuth header on the app. The newline was encrypted into the token, so it was not obvious from looking at the configs or anything that it was there, but if you do:
var message = new HttpRequestMessage(HttpMethod.Post, "https://example.com");
message.Headers.ContentType = new MediaTypeHeaderValue("application/json");
message.Content = new StringContent("{ \"someKey\": \"someValue\" }", Encoding.UTF8);
// note the trailing newline
message.Headers.Authorization = new AuthenticationHeaderValue("OAuth", "my auth token\n");
var response = await httpClient.SendAsync(request);
The HTTP request will be sent, but the content will not be sent with it. There are no exceptions thrown when this happens and if you inspect the HttpRequestMessage, the content will appear to be there, but it does not actually get sent over the wire.
This happens in .NET 5 on Windows and Linux, I haven't tested it on other framework versions/platforms.

Categories