I'm attempting to pass username/password from an application to the API to receive a token authorization key. When I attempt to do so, I receive a 400 Bad Request error and I cannot figure out why. Below is the method in question:
public User UserAuthentication(string username, string password)
{
string endpoint = baseURL + "/TOKEN";
// Could be POST maybe
string method = "POST";
Credential jsonObj = new Credential
{
grant_type = "password",
username = username,
password = password
};
string jsonStr = JsonConvert.SerializeObject(jsonObj);
WebClient wc = new WebClient();
//x - www - form - urlencoded
wc.Headers[HttpRequestHeader.ContentType] = "application/x - www - form - urlencoded";
wc.Headers.Add("Access-Control-Allow-Headers", "content-type");
wc.Headers.Add("Access-Control-Allow-Origin", "*");
wc.Headers[HttpRequestHeader.Authorization] = "Bearer <token>";
wc.Headers.Add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, OPTIONS");
string header = wc.Headers.ToString();
try
{
string response = wc.UploadString(endpoint, method, jsonStr);
return JsonConvert.DeserializeObject<User>(response);
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
I've messed around altering just about everything in this method in search of a fix.
What I've done:
/TOKEN was /values & /api/values
POST method was GET -- With this, I received a "Cannot send a content-body with this verb-type." error.
ContentType was changed to "application/json"
Access-Control-Allow-Origin had the baseURL
Checked the format of header & body:
Header:
{Content-Type: application/x - www - form - urlencoded
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Origin: *
Authorization: Bearer <token>
Access-Control-Allow-Methods: POST, PUT, GET, DELETE, OPTIONS}
Body:
{"grant_type":"password",
"username":"test#gmail.com",
"password":"password123"}
I obviously have something wrong in my request, I've just run out of ideas to try. I'm not entirely sure if UploadString() is the correct method to be using in this situation, but I couldn't find another method in the WebClient class that would be better. Any help to try and push me in the right direction would be very much appreciated.
So what I think you are trying to do is a form-urlencoded post to a "token" endpoint with a username/password grant. These are typically done like so:
using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri("https://example.com/token"))
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", "username#site.com" },
{ "password", "password12345" }
})
})
{
using (var resp = await _client.SendAsync(request))
{
resp.EnsureSuccessStatusCode();
//await resp.Content.ReadAsAsync<BearerToken>();
// for testing purposes, try this:
var returnData = await resp.Content.ReadAsStringAsync();
Console.WriteLine(returnData);
}
}
You should define this outside all scopes where you need to do Http requests:
private static readonly HttpClient _client = new HttpClient();
So, first off, try to stick with HttpClient. Other patterns such as WebClient are considered legacy.
Next, CORS headers are typically returned from the server when an OPTIONS call is sent to the server. You aren't doing that here, and you should never have to worry about that kind of stuff inside a C# program running from your computer. So you can drop the access-control header stuff.
Form-urlencoded data is not JSON data. It's a different way to format data. If you want to send JSON data, you should use the content-type application/json
Finally, you are trying to add an Authorization header. But that doesn't make much sense as you are trying to authenticate yourself to become authorized. If you send the right username/password, you will receive a bearer token that you can use in an Authorization header for future requests to said service.
Oh and I forgot to add: Whenever you see an error in the [400,499] range (in this case "400 - bad request") it means that you sent something wrong and the server doesn't understand what you are trying to do. For example: a 401 means you sent invalid or missing authorization information. A 400 means your data was probably malformed.
But I like your question... I can see what you were doing and you tried all kinds of different things.
Download a program called Fiddler if you want to see how HTTP works. It's a great tool to debug your HTTP calls.
Related
I am attempting to integrate with the Eventbrite API, I am just trying to create an event using RestSharp.
// Request to create an event.
var restClient = new RestClient("https://www.eventbriteapi.com/v3/");
This is the base url I am using {MyOrganiserID} and MyToken to replace my actual token.
var createEventRequest = new RestRequest("organizations/{MyOrganiserID}/events", Method.POST);
createEventRequest.AddHeader("Authorization", "Bearer MyToken");
createEventRequest.AddHeader("content-type", "application/x-www-form-urlencoded");
createEventRequest.AddQueryParameter("event.name.html", "My Event is good mate");
createEventRequest.AddQueryParameter("event.start.utc", "2019-12-12T18:00:00Z");
createEventRequest.AddQueryParameter("event.start.timezone", "Australia/Melbourne");
createEventRequest.AddQueryParameter("event.end.utc", "2019-12-12T20:00:00Z");
createEventRequest.AddQueryParameter("event.end.timezone", "Australia/Melbourne");
createEventRequest.AddQueryParameter("event.currency", "AUD");
IRestResponse createEventRestResponse = restClient.Execute(createEventRequest);
var requestContent = createEventRestResponse.Content;
I have also tried to send the parameters in the requestbody using AddBody and AddXMLBody from the RestSharp API.
createEventRequest.AddBody("event.name.html=<p>A DARQ Room Production Woof Woof</p>&event.start.utc=2019-12-12T18:00:00Z&event.start.timezone=Australia/Melbourne&event.end.utc=2019-12-12T20:00:00Z&event.end.timezone=Australia/Melbourne&event.currency=AUD");
createEventRequest.AddXmlBody("event.name.html=<p>A DARQ Room Production Woof Woof</p>&event.start.utc=2019-12-12T18:00:00Z&event.start.timezone=Australia/Melbourne&event.end.utc=2019-12-12T20:00:00Z&event.end.timezone=Australia/Melbourne&event.currency=AUD");
I've also tried to add the fields via the AddParameters method as well.
createEventRequest.AddParameter("event.name.html", "This is a good event mate");
createEventRequest.AddParameter("event.start.utc", "2019-12-12T18:00:00Z");
createEventRequest.AddParameter("event.start.timezone", "Australia/Melbourne");
createEventRequest.AddParameter("event.end.utc", "2019-12-12T20:00:00Z");
createEventRequest.AddParameter("event.end.timezone", "Australia/Melbourne");
createEventRequest.AddParameter("event.currency", "AUD");
With the AddQueryParameter I get a 401 Unauthorised I have tried putting the token as a query parameter aswell and it still says I'm unauthorized.
And another error I get is
{
"status_code": 403,
"error_description": "You do not have permission to access the resource you requested.",
"error": "NOT_AUTHORIZED"
}
When I send the parameter via the request body.
Any help anyone can provide would be deeply appreciated.
I have been attempting to do a simple POST request to a basic Flask API, and no matter what I try it simply will do http://localhost:5000/ rather than the http://localhost/?test_key=test_value that I want it to do. C# is not my strongest language, but I have tried a bunch of different methods that I found online. I don't think it has to do with my method being bad at this point, I more so think that I'm missing a major piece entirely.
Here is the most recent attempt I tried (keep in mind this is a snippet of a far larger project, but it doesn't involve any other pieces of it):
class DisputeResponse
{
public int id;
public string res;
public string test_key;
}
[HttpPost]
public async Task<JsonResult> TestResponse(int id, string res)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:5000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
var dispRes = new DisputeResponse();
dispRes.test_key = "test_value";
var result = await client.PostAsync("http://localhost:5000/",
new StringContent(JsonConvert.SerializeObject(dispRes), Encoding.UTF8, "application/json"
));
result.EnsureSuccessStatusCode();
}
catch (Exception e)
{
System.Diagnostics.Trace.TraceInformation(e.Message);
}
return null;
}
Here is the output when I use Postman (works perfectly):
127.0.0.1 - - [15/Apr/2019 16:26:28] "POST /?test_key=test_value HTTP/1.1" 200 -
And here is when I try to use C# code:
127.0.0.1 - - [15/Apr/2019 16:36:54] "POST / HTTP/1.1" 500 -
EDIT: Sorry I had some extraneous lines that I removed.
The HTTPClient is putting the parameters inside the body of the request. If you would like to add it to the URI you can do so by modifying the URI string. If you are not posting anything to the body, I would modify the FLASK API to receive a GET instead of a POST.
Typically I would only use query string parameters in the URI string for HTTPGet requests, even though there is no rule stopping you from doing this with a HTTPPost. Additionally, I would only include my parameters for an HTTPPost request within the body. Again, no rule for this but just what I do.
Here are a few examples to help you along with the HTTPClient post async method:
https://csharp.hotexamples.com/examples/-/HttpClient/PostAsync/php-httpclient-postasync-method-examples.html
I went through the OAuth2 proccess in DocuSign API, I follow all the steps using official docs, but when I tried to perform the request in order to get the the AccessToken I received an HTML as response, indicating something like "DocuSign is temporarily unavailable. Please try again momentarily." Although the http response is 200(OK), The weird stuff is when I test with the same values on Postman I get the correct response.
This is my code
public static DocuSignBearerToken GetBearerToken(string AccessCode, bool RefreshToken = false)
{
string AuthHeader = string.Format("{0}:{1}", DocuSignConfig.IntegratorKey, DocuSignConfig.SecretKey);
var client = new RestClient("http://account-d.docusign.com");
client.Authenticator = new HttpBasicAuthenticator(DocuSignConfig.IntegratorKey, DocuSignConfig.SecretKey);
var request = new RestRequest("/oauth/token", Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddHeader("authorization", "Basic " + Base64Encode(AuthHeader));
if(!RefreshToken)
request.AddParameter("application/x-www-form-urlencoded", string.Format("grant_type=authorization_code&code={0}", AccessCode), ParameterType.RequestBody);
else
request.AddParameter("application/x-www-form-urlencoded", string.Format("grant_type=refresh_token&refresh_token={0}", AccessCode), ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var responseString = response.Content;
DocuSignBearerToken Result = JsonConvert.DeserializeObject<DocuSignBearerToken>(responseString);
return Result;
}
Ok, this is awkward, reading the DocuSign docs they never specify if the authorization URL is http or https I assumed it was http, postman is smart enough to determine http or https when performs the request, my code doesn't, simply changing the Authorization URL from http:// to https:// solves the error.
If your tests using Postman work, then there is a problem with your code.
We've all been there, including me!
In these cases, I send my request to requestb.in to see what I'm really sending to the server. You'll find something is different from what you're sending via Postman.
I built a C# application about 8 months ago to pull SurveyMonkey responses and store them on our SQL Server. The application has run every day for over 6 months without any issue, but suddenly Friday morning I can't get any requests to go through. I'm not seeing anything on the developer site mentioning outages, so I've been examining my code for possible issues.
This is the first app I've ever written to create web requests, so it's possible I'm doing things badly.
Request, from Fiddler:
POST https://api.surveymonkey.net/v2/surveys/get_survey_list?api_key=<key hidden> HTTP/1.1
Authorization: bearer <token hidden>
Content-Type: application/json
Host: api.surveymonkey.net
Content-Length: 146
Expect: 100-continue
Connection: Keep-Alive
{
"page": 1,
"fields": ["title","analysis_url","preview_url","date_created","date_modified","language_id","question_count","num_responses"]
}
Response body, from Fiddler:
{"status":1,"errmsg":"Request header \"Authorization\" token not found"}
C# code:
private JObject SubmitPostRequest(string URIPath, string RequestBody)
{
using (var webClient = new WebClient())
{
// Create URI for POST request to go to. URI varies depending on API method and includes API Key.
Uri requestURI = new Uri(APIHost, URIPath + "?api_key=" + APIKey);
// Have web client use NT credentials for proxy server
webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
// Specify headers: access token in authorization header, content type
webClient.Headers["Authorization"] = "bearer " + AccessToken;
webClient.Headers["Content-Type"] = "application/json";
// Create requestBody as byte[]
byte[] requestBody = Encoding.Default.GetBytes(RequestBody);
// Connect to the URI and send requestBody as a POST command.
byte[] postResponse = webClient.UploadData(requestURI, "POST", requestBody);
// Update LastConnect
LastConnect = DateTime.Now;
++TransactionCounter;
// Convert byte[] response to string, parse string and return as JObject.
JObject jsonResponse = JObject.Parse(Encoding.Default.GetString(postResponse));
// Evaluate "status" field of jsonResponse. Throw exception if response is not 0.
dynamic dyn = jsonResponse;
if (dyn.status != 0)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("HTTP Post request failed:");
sb.Append("\tstatus: ").AppendLine(dyn.status.ToString());
sb.Append("\terrmsg: ").AppendLine(dyn.errmsg.ToString());
throw new WebException(sb.ToString());
}
return jsonResponse;
}
}
It took awhile, but I managed to get in touch with someone in corporate IT who monitored the traffic to see what was going on. They implemented a fix to the proxy server and everything is finally working again.
I created RESTful webservice (WCF) where I check credentials on each request. One of my clients is Android app and everything seems to be great on server side. I get request and if it's got proper header - I process it, etc..
Now I created client app that uses this service. This is how I do GET:
// Create the web request
var request = WebRequest.Create(Context.ServiceURL + uri) as HttpWebRequest;
if (request != null)
{
request.ContentType = "application/json";
// Add authentication to request
request.Credentials = new NetworkCredential(Context.UserName, Context.Password);
// Get response
using (var response = request.GetResponse() as HttpWebResponse)
{
// Get the response stream
if (response != null)
{
var reader = new StreamReader(response.GetResponseStream());
// Console application output
var s = reader.ReadToEnd();
var serializer = new JavaScriptSerializer();
var returnValue = (T)serializer.Deserialize(s, typeof(T));
return returnValue;
}
}
}
So, this code get's my resource and deserializes it. As you see - I'm passing credentials in my call.
Then when debugging on server-side I noticed that I get 2 requests every time - one without authentication header and then server sends back response and second request comes bach with credentials. I think it's bad for my server - I'd rather don't make any roundtrips. How should I change client so it doesn't happen? See screenshot of Fiddler
EDIT:
This is JAVA code I use from Android - it doesn't do double-call:
MyHttpResponse response = new MyHttpResponse();
HttpClient client = mMyApplication.getHttpClient();
try
{
HttpGet request = new HttpGet(serviceURL + url);
request.setHeader(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
request.addHeader("Authorization", "Basic " + Preferences.getAuthorizationTicket(mContext));
ResponseHandler<String> handler = new BasicResponseHandler();
response.Body = client.execute(request, handler);
response.Code = HttpURLConnection.HTTP_OK;
response.Message = "OK";
}
catch (HttpResponseException e)
{
response.Code = e.getStatusCode();
response.Message = e.getMessage();
LogData.InsertError(mContext, e);
}
The initial request doesn't ever specify the basic header for authentication. Additionally, since a realm is specified, you have to get that from the server. So you have to ask once: "hey, I need this stuff" and the server goes "who are you? the realm of answering is 'secure area'." (because realm means something here) Just because you added it here:
request.Credentials = new NetworkCredential(Context.UserName, Context.Password);
doesn't mean that it's going to be for sure attached everytime to the request.
Then you respond with the username/password (in this case you're doing BASIC so it's base64 encoded as name:password) and the server decodes it and says "ok, you're all clear, here's your data".
This is going to happen on a regular basis, and there's not a lot you can do about it. I would suggest that you also turn on HTTPS since the authentication is happening in plain text over the internet. (actually what you show seems to be over the intranet, but if you do go over the internet make it https).
Here's a link to Wikipedia that might help you further: http://en.wikipedia.org/wiki/Basic_access_authentication
Ok, I got it. I manually set HttpHeader instead of using request.Credentials
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(Context.UserName + ":" + Context.Password)));
Now I see only single requests as expected..
As an option you can use PreAuthenticate property of HttpClientHandler. This would require a couple of lines more
var client = new HttpClient(new HttpClientHandler
{
Credentials = yourCredentials,
PreAuthenticate = true
});
With using this approach, only the first request is sent without credentials, but all the rest requests are OK.