Why is postman sending form data in an HTTP GET? - c#

I received a Postman json collection from an API vendor that works perfectly, but has something mystifying to me: The request is in a GET format, yet there is an x-www-form-urlencoded body.
URL: https://login.microsoftonline.com/d1e<secret>9563/oauth2/token
And when I look at the postman-generated c# code, the mystery continues:
var client = new RestClient("https://login.microsoftonline.com/d1e...d3/oauth2/token");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "c06bb...79");
request.AddParameter("client_secret", "7~u...D");
request.AddParameter("resource", "https://vault.azure.net");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Note the AddParameter constructions for a GET call. To me, this must be a slight-of-hand for merely adding those values to the querystring. But when I look at the postman console I see:
In the postman console I would have expected to see those params appended to the url as a querystring, and then everything would have made sense. But you can see that it's a bonafide Request Body.
When I make GET calls in my c# code I like to use the simple yet solid WebClient object to call the DownloadString() method. But this method is only for GETs and there's no way to send a form-post style body, understandably.
Is postman truly sending a GET with all those values being appended to the url as a querystring? And should I do the same in my DownloadString() call? Or is there something else going on here? Should I instead, in my c#, be calling the UploadString() method and sending a form post BODY as a GET??

Http protocol supports adding a body to a request, but the WebClient class you use doesn't. Presumably because it isn't considered the norm.
I'm sure there's good reasons for Microsoft using it in the OAuth flow though. Those guys normally do things right!
HTTP GET with request body

API is just an abstraction , you can send what ever you want to the API . It depends on the implementation , how the server handles these data.
Some services considers only what it requires and ignores other information
some services considers the entire requests and validates that it has only the allowed data. what should be allowed depends on the service
Postman is just a client that sends data to server , its upto you to decide what all information it should send . If you dont need any body then keep it as none. if you need some thing then add it.

Related

Restsharp parameter behavior when setting ParameterType=GetOrPost

I face a weird intermittent issue with RestSharp. I use it to submit a POST authentication request to a server. But under certain conditions that I cannot isolate yet, this authentication call fails. By looking at the server log, I see a GET request instead of a POST. And I really wonder how the hell it is possible.
Here is the code to submit the authentication request :
var client = new RestClient(m_baseUrl);
var request = new RestRequest("https://dummyserver.com/api/auth", Method.POST);
request.AddParameter("client_id", apiCredentials.ApiKey, ParameterType.GetOrPost);
request.AddParameter("client_secret", apiCredentials.ApiSecret, ParameterType.GetOrPost);
request.AddHeader("Content-Type", "multipart/form-data");
IRestResponse response = await client.ExecutePostTaskAsync(request);
Since it is a distributed application, I don't have much info on the client request. Telemetry just confirms that the authentication failed.
Is it possible (known bug) that RestSharp transformed this request into a GET?
And, second question, is there any difference on the request being created with those two syntaxes:
request.AddParameter("client_id", apiCredentials.ApiKey, ParameterType.GetOrPost);
request.AddParameter("client_id", apiCredentials.ApiKey);
I need parameters to be submitted as form-data for security purposes.
Thanks for your help.
I don't think it's possible that a POST request gets executed as GET. Consider that you have some code (not the code from your question), which does that. Even using ExecutePost is redundant when you explicitly set the request type in the RestRequest constructor.
Concerning the second question, there's no real difference. The default parameter type is GetOrPost. I also believe that we use multipart/form-data for POST requests by default where there's no JSON or XML body.
You can easily find it by looking at the code of RestSharp.
Although weird, I've experienced a similar issue with you.
My solution is to use ExecuteAsPost():
var response = client.ExecuteAsPost(request, "POST");

Generating Multipart form data body from text for RestSharp request

I'm trying to get a basic login feature working using RestSharp. I have the login user and password as text that I need to convert somehow since the server accepts requests as multipart/form-data and my initial attempt was this:
RestRequest request = new RestRequest(resource, Method.POST);
request.AddParameter("login", config.Login, ParameterType.RequestBody);
request.AddParameter("pass", config.Password, ParameterType.RequestBody);
Only to find out I cannot add two params for request body, where it discards all but the first. So my next attempt was to build the requestBody:
MultipartFormDataContent formData = new MultipartFormDataContent();
formData.Add(new StringContent(config.Login), "login");
formData.Add(new StringContent(config.Password), "pass");
request.AddBody(formData);
This also didnt work and just returned the closed element </MultipartFormDataContent>
Last I tried to just pass them as post params rather than the body, which it seems to have just ignored considering it expects a form I guess:
request.AddParameter("login", JsonConvert.SerializeObject(config.Login), ParameterType.GetOrPost);
request.AddParameter("pass", JsonConvert.SerializeObject(config.Password), ParameterType.GetOrPost);
I'm looking for any tips on how I can somehow convert and send this text as valid form-data. It looks easier to do with HTTPWebRequest rather than RestSharp but it's bothering me that I'm sure it's possible.
Thanks so much!
It turns out that the default headers generated by RestSharp were sending the boundary parameter with quotes, where my endpoint was receiving them without quotes, so I needed a lower level interface and ended up being able to create a custom header for boundary with HttpWebRequest and MediaHeadervalue that fit my needs. Thanks everyone for looking!

How do I get RestSharp not to add oauth_callback parameters to the Authentication header

I'm trying to connect to a finicky API using RestSharp. The API uses OAuth1.0 and on the initial Request Token requires oauth_callback parameter ONLY in the query and not in the Authentication Header (I have confirmed this with Postman).
When I construct the request this way:
var Authenticator = OAuth1Authenticator.ForRequestToken(mc_apiKey, mc_appsecret);
Authenticator.ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader;
Authenticator.SignatureMethod = OAuthSignatureMethod.PlainText;
client.Authenticator = Authenticator;
var request = new RestRequest(RequestToken, Method.POST);
string AuthorizationCallBackURL = string.Format(LoopbackCallback);
request.AddParameter(_oauthCallback, AuthorizationCallBackURL, ParameterType.QueryStringWithoutEncode);
and look at the logs on the server I see the query string in the Http call,
http://192.168.0.187:8080/xxxx/ws/oauth/initiate?oauth_callback=http://192.168.0.187:8080/provider_emailer/callback.jsp
but it is also in the Authentication header:
Headers:
{Accept=[application/json, text/json, text/x-json, text/javascript, application/xml, text/xml],
accept-encoding=[gzip, deflate],
Authorization=[OAuth oauth_callback="http://192.168.0.187:8080/provider_emailer/callback.jsp",
oauth_consumer_key="XXXXXXXXXXXX",
oauth_nonce="cei09xm04qetk2ce",
oauth_signature="XXXXXXXXXXXXXXXX",
oauth_signature_method="PLAINTEXT",
oauth_timestamp="1591197088",
oauth_version="1.0"],
Content-Length=[0], Content-Type=[null],
cookie=[JSESSIONID=C8C8DB501382F7D1E52FE436600094C0],
host=[192.168.0.187:8080], user-agent=[RestSharp/106.11.4.0]}
This causes a "NotAcceptable" response. The same request done with Postman without the callback parameter in the Authentication header works.
Am I doing something wrong? Is there a way to only get the callback in the query string?
That's tricky. I looked at the code and we don't set the callback URL to the workflow object when you use the overload without this parameter. So, you're doing it conceptually correct.
However, we must collect all the parameters (default and request parameters) plus OAuth parameters to build the OAuth signature. We then use the parameters collection and extract all of them that have a name starting with oauth_ or xauth_ to the request headers (in case you use HttpAuthorizationHeader) and by doing so, we put your query parameter to the header.
Apparently, that's not ideal and it looks like a bug, so I can suggest opening an issue in RestSharp repository. Should not be hard to fix.

Is there a way to retrieve the String the way it is actually uploaded to the server (as a whole)?

I am currently working on a OAuth2 implementation. However I am stuck on an Error 401. It seems like there is something wrong with my post request that is supposed to retrieve the access token from the Company the User logged in to. This is my code:
internal void RequestAccessToken(string code)
{
string requestBody = "grant_type="+ WebUtility.UrlEncode(GRANTTYPE)+ "&code=" + WebUtility.UrlEncode(code)+"&redirect_uri="+ WebUtility.UrlEncode(REDIRECT_URI);
WebClient client = new WebClient();
client.Headers.Add("Authorization",HeaderBase64Encode(CLIENT_ID, SECRETKEY));
var response = client.UploadString("https://thewebsiteiamcallingto.com/some/api", requestBody);
var responseString = client.OpenRead("https://thewebsiteiamcallingto.com/some/api");
}
My Questions are:
Is there anything wrong with the way I try to make the POST request ?
Is there a way to retrieve the whole string that is posted to the URI using UploadString?
P.S. I have seen this post regarding the POST creation. However I find the async part to be too complicated for my case.
Since we dont know the api documentation, I would suggest you to make a postman request and view the actual request sent and response received, and secondly make a request using your method and capture using a utility like wireshark and compare the difference.

How to clean up existing response in webapi?

There is a authentication library that I have to use that helpfully does things like
Response.Redirect(url, false);
inside of it's method calls. I can't change this libraries code and it's fine for MVC style apps but in angular SPA -> WebApi apps this is just awful.
I really need a 401 otherwise I get into trouble with CORS when my angular scripts, using $http, try to call out to the auth server on another domain in response to the 302, that's if it even could as the Response.Redirect also sends down the object moved html and the angle brackets cause an error to be thrown.
Since I have to make the call to the auth library first the Response.Redirect is already in the response pipeline and so I need to clean it up to remove the body content and convert the 302 into a 401. I thought I could just:
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent("data");
}
but this just gets appended to the response and doesn't replace it plus I also need the Location: header which I can't seem to access via WebApi methods.
So instead I've had to do this in my ApiController:
var ctxw = this.Request.Properties["MS_HtpContext"] as HttpContextWrapper;
var ctx = ctxw.ApplicationInstance.Context;
var url = ctx.Response.RedirectLocation;
ctx.Response.ClearContent();
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent(url);
}
But this seems terrible and counter to webapi "feel". Plus I'm tied to the controller in doing this. I can't get the wrapper in a MessageHandler for example.
What I'd like to do is monitor the response for a given route in a message handler or in an AuthorizationFilterAttribute, if its a 302, I want to read it's headers, take what I want, wipe it and replace it with my own "fresh" response as a 401. How can I do this?
You might want to write your own ActionFilter and override its OnActionExecuted method where you can access HttpActionExecutedContext. From there, you can check response code, for example, and overwrite response with whatever you want.
Ref: https://msdn.microsoft.com/en-us/library/system.web.http.filters.actionfilterattribute.onactionexecuted%28v=vs.118%29.aspx#M:System.Web.Http.Filters.ActionFilterAttribute.OnActionExecuted%28System.Web.Http.Filters.HttpActionExecutedContext%29

Categories