Bing Webmaster Tools API OAuth code exchange issues, changes? - c#

This is part of a desktop application.
Based on https://learn.microsoft.com/en-us/bingwebmaster/oauth2
The following code to exchange the authorization code for the access and refresh tokens was working as of a few months ago...
try
{
HttpWebRequest req = WebRequest.CreateHttp("https://www.bing.com/webmasters/oauth/token");
req.Method = WebRequestMethods.Http.Post;
req.ContentType = "application/x-www-form-urlencoded";
StringBuilder content = new StringBuilder();
content.AppendFormat("code={0}&", Uri.EscapeDataString(code));
content.AppendFormat("client_id={0}&", Uri.EscapeDataString(clientId));
content.AppendFormat("client_secret={0}&", Uri.EscapeDataString(clientSecret));
content.AppendFormat("redirect_uri={0}&", Uri.EscapeDataString(redirectUri));
content.AppendFormat("grant_type={0}", Uri.EscapeDataString("authorization_code"));
var data = Encoding.ASCII.GetBytes(content.ToString());
using (var stream = await req.GetRequestStreamAsync())
{
await stream.WriteAsync(data, 0, data.Length);
}
string json;
using (var res = await req.GetResponseAsync())
{
using (var stream = res.GetResponseStream())
using (var sr = new StreamReader(stream))
{
json = await sr.ReadToEndAsync();
}
}
if (!string.IsNullOrWhiteSpace(json))
{
tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(json);
}
}
catch (WebException wex)
{
using (var stream = wex.Response.GetResponseStream())
using (var sr = new StreamReader(stream))
{
var t = await sr.ReadToEndAsync();
}
}
catch (Exception ex)
{
}
However, await req.GetResponseAsync() now returns
400 Bad Request, Origin and Referer request headers are both
absent/empty
I tried adding req.Referer = redirectUri; and then it returns
400 Bad Request, Could not extract expected anti-forgery token
I've tried passing a random state parameter to the authorization endpoint, and received the same in the callback. I've both included it in, and excluded it from, the token exchange with no change in the above results.
I'm not an OAuth expert, but I've done a few integrations and I haven't seen this before.
The user grants authorization via a Window with a WebView2 control, which returns the code. This part still works well. I did some quick poking around in the response to see if anything related to anti-forgery/CSRF was being returned from the server, but I didn't notice anything. And anyway, the documentation hasn't change regarding what is needed to request the tokens so everything is basically trial and error at this point.
So my question is, if you have seen this referer/anti-forgery problem in any OAuth implementation how did you fix it or work around it? Or if you're using a Bing Webmaster Tools API solution (custom or otherwise) is it still working?
Beyond that, I'm open to ideas and I appreciate your time.

Related

REST API for coinspot in C#

I am doing a bit of research into building a program to use the Australian crypto trading platform Coinspot. I have used API's in the past but barley used "POST" method, Coinspots information and documentation is sooooooo poor (probs because they don't want you to use it) but i figured ill give it a whirl anyways.
Question:
How do you use a POST method if you do not know the order in which to send data? (is there a standard or is every API different).
i have an API key and a secret API key to send for authorisation but still get access denied.
Is there anyway you can use code to request an order or something like you do when you pull a json to string?
WebRequest request = WebRequest.Create("https://www.coinspot.com.au/api/ro/balances");
request.Method = "POST";
request.Headers.Add("key", APIKEY);
request.ContentType = "application/json";
request.GetResponse(); //THIS IS WHERE IT ERRORS WITH AUTHENTICATION
sorry if its a scrub question but i think im looking at this the right way, i am also using .NET library json newtonsoft so any information on this would be fantastic!!
.NET Core interpretation of the somewhat dated yet functional CoinSpot node.js api client
var nonce = Utility.GetEpochTime();
var postData = new { nonce };
var jsonMessage = postData.ToJson();
var hmac = new HMACSHA512(Encoding.ASCII.GetBytes("CoinSpotSecret"));
var byteArray = Encoding.ASCII.GetBytes(jsonMessage);
using (var stream = new MemoryStream(byteArray)) {
var hash = hmac.ComputeHash(stream).Aggregate("", (s, e) => s + String.Format("{0:x2}", e), s => s);
var uri = new Uri("https://www.coinspot.com.au/api/ro/my/balances", UriKind.Absolute);
var content = new StringContent(
jsonMessage,
System.Text.Encoding.UTF8,
"application/json"
);
content.Headers.Add("sign", hash);
content.Headers.Add("key", "CoinSpotKey");
var response = await httpClient.PostAsync(uri, content);
if (response.IsSuccessStatusCode) {
return await response.Content.ReadAsStringAsync();
}
}

Google Drive insert file permission

I can't insert permission to a file with this code:
string URI = String.Format("https://www.googleapis.com/drive/v2/files/{0}/permissions&access_token={1}", fileId, "token");
var request = (HttpWebRequest)HttpWebRequest.Create(URI);
request.Method = "POST";
request.ContentType = "application/json";
string json = "{\"role\": \"reader\",\"type\": \"anyone\"}";
byte[] byteData = new System.Text.ASCIIEncoding().GetBytes(json);
request.ContentLength = byteData.Length;
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(byteData, 0, byteData.Length);
}
var response = (HttpWebResponse)request.GetResponse();
using (var reader = new StreamReader(response.GetResponseStream()))
{
json = reader.ReadToEnd();
}
I al getting a 404 error. What's the problem?
string URI = String.Format("https://www.googleapis.com/drive/v2/files/{0}/permissions&access_token={1}", fileId, "token");
Access token is not a string "token" it must be a valid access token for the user who owns the file.
Update:
permissions?access_token={1}",
You should be using ? and not & to add a parameter to the url. Not even sure you can do it like that with a HTTP Post.
Added info:
If this is not simply a typo on your part you may want to read up on Authorization a little
I also recommend checking out the Google client library instead of writing this yourself. Google.Apis.Drive.v2 Client Library.
There is a newer version of the Google Drive API you might also be interested in looking at rather then writing new code for an older version of the API. Google Drive API v3.

Unable to create a shared link using the Box API V2

UPDATE: I figured it out and posted the answer below.
All I'm trying to do is update any file attribute. Description, name, anything, but no matter how I format it I get a 403.
I need to be able to modify a file so it can be shared via the Box API from a cloud app. I'm updating someone else's code from V1, but they are no longer available... I've tried many things but mostly just get 403 Forbidden errors.
There are no issues with OAuth2, that works fine and I can list files and folders, but can not modify them. This question is about sharing, but I can't change a description either. The box account is mine and I authenticate with my admin credentials. Any suggestions would be appreciated.
Here is the method I am using. I pass in the fileId and token and I've left out try/catch etc. for brevity.
string uri = string.Format("https://api.box.com/2.0/files/{0}", fileId);
string body = "{\"shared_link\": {\"access\": \"open\"}}";
byte[] postArray = Encoding.ASCII.GetBytes(body);
using (var client = new WebClient())
{
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
client.Headers.Add("Authorization: Bearer " + token);
var response = client.UploadData(uri, postArray);
var responseString = Encoding.Default.GetString(response);
}
Thanks.
Okay, My Homer Simpson moment...
UploadData is a POST, I needed to do a PUT. Here is the solution.
string uri = String.Format(UriFiles, fileId);
string response = string.Empty;
string body = "{\"shared_link\": {\"access\": \"open\"}}";
byte[] postArray = Encoding.ASCII.GetBytes(body);
try
{
using (var client = new WebClient())
{
client.Headers.Add("Authorization: Bearer " + token);
client.Headers.Add("Content-Type", "application/json");
response = client.UploadString(uri, "PUT", body);
}
}
catch (Exception ex)
{
return null;
}
return response;
try changing your content type to 'multipart/form-data'?
I just looked up the api at: https://developers.box.com/docs/#files-upload-a-file
and it looks like the server is expecting a multipart post
here is stack overflow post on posting multipart data:
ASP.NET WebApi: how to perform a multipart post with file upload using WebApi HttpClient

oAuth 2 with Open API

I'm trying to authorize a console app to an API that uses oAuth 2.0. I've tried DotNetOpenAuth with no success.
First step is to get the Authorization code, this is what I have but I cannot find the authorization code to proceed to step 2 (Authorization using pre-defined authorization code, with User ID and User password). I have used the following authorization headers from the documentation
https://sandbox-connect.spotware.com/draftref/GetStartAccounts.html#accounts?
In the response I do not get any headers with authentication code, how can I solve this?
string sAuthUri = "https://sandbox-connect.spotware.com/oauth/v2/auth";
string postData ="access_type=online&approval_prompt=auto&client_id=7_5az7pj935owsss8kgokcco84wc8osk0g0gksow0ow4s4ocwwgc&redirect_uri=http%3A%2F%2Fwww.spotware.com%2F&response_type=code&scope=accounts";
string sTokenUri = "https://sandbox-connect.spotware.com/oauth/v2/token";
var webRequest = (HttpWebRequest)WebRequest.Create(sAuthUri);
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
try
{
using (Stream s = webRequest.GetRequestStream())
{
using (StreamWriter sw = new StreamWriter(s))
sw.Write(postData.ToString());
}
using (WebResponse webResponse = webRequest.GetResponse())
{
using (var reader = new StreamReader(webResponse.GetResponseStream()))
{
response = reader.ReadToEnd();
}
}
var return = response;
}
catch (Exception ex)
{
}
Using the Auhtorization code flow of OAuth2, the code should come in the query string of the redirection to redirect_uri.
I think that the problem is in the call method (should be GET), with postData as QueryString, and the redirect_uri must be a url of your system (not spotware). The authorization code should be retrieved from query string of the webResponse.
I recommend you to use DotNetOpenAuth library to implement OAuth2 requests easily.

Login session not transferring to new webpage using WebRequest/Response?

Ok, I've been racking my brain on this one solo for too long. I've been unable to crack it even with hours spent on this and many other sites.
Essentially, I'm trying to strip some data from a webpage behind a LogIn page using WebRequest/Response. (I have gotten this to work using a WebBrowser control with some layered events which navigate to the different web pages but it's causing some problems when trying to refactor - not to mention it's been stated that using a hidden form to do the work is 'bad practice'.)
This is what I have:
string formParams = string.Format("j_username={0}&j_password={1}", userName, userPass);
string cookieHeader;
WebRequest request = WebRequest.Create(_signInPage);
request.ContentType = "text/plain";
request.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes(formParams);
request.ContentLength = bytes.Length;
using (Stream os = request.GetRequestStream())
{
os.Write(bytes, 0, bytes.Length);
}
WebResponse response = request.GetResponse();
cookieHeader = response.Headers["Set-Cookie"];
WebRequest getRequest = WebRequest.Create(sessionHistoryPage);
getRequest.Method = "GET";
getRequest.Headers.Add("Cookie", cookieHeader);
WebResponse getResponse = getRequest.GetResponse();
try
{
using (StreamReader sr = new StreamReader(getResponse.GetResponseStream()))
{
textBox1.AppendText(sr.ReadToEnd());
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
So far, I'm able to get to the proper page from the first link but when I go to the second, it sends me back to the login page as if I didn't log in.
The problem may lie in cookies not getting captured correctly but I'm a novice so maybe I'm just doing it wrong. It captures the cookies sent back from the POST: JSESSIONID and S2V however, when we go to the "GET", using FireFox WebConsole, the browser shows that it sends JSESSIONID, S2V and a SPRING_SECURITY_REMEMBER_ME_COOKIE, which I believe is the cookie used when I click the "Remember Me" box on the login form.
I've tried many different ways of doing this using the resources of SO but I have yet to get to the page I need. So, for the sake of the hair I have left, I've decided to ask for help on good ole SO. (This is one of those things I don't want to let up on - stubborn like that sometimes)
If someone wants the actual address of the site I'm trying to log into, I'd be more than happy to send it to a couple people in a private message.
Code that I have to reflect a suggested answer by Wal:
var request = (HttpWebRequest)WebRequest.Create(sessionHistoryPage);
request.Credentials = new NetworkCredential(userName, userPass);
request.CookieContainer = new CookieContainer();
request.PreAuthenticate = true;
WebResponse getResponse = request.GetResponse();
try
{
using (StreamReader sr = new StreamReader(getResponse.GetResponseStream()))
{
textBox1.AppendText(sr.ReadToEnd());
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
This suggestion, at least the way I implemented it, didn't work.
As Krizz suggested, I changed the code to use CookieContainer and transferring the cookies from one request to the other however, the response just gives me back the original login page as if I didn't login.
Are there certain sites that just WILL NOT allow this type of behavior?
Final Solution
The final solution was proposed by Adrian Iftode where he stated that the website I'm trying to log in might not allow to have an authentication without a valid session so adding a GET to the beginning of the process allowed me to get that cookie.
Thanks for all your help guys!
I was doing some sort of cookie transfer for a website written with PHP.
Clearly you are passing the cookie, but maybe is like in that situation
var phpsessid = response.Headers["Set-Cookie"].Replace("; path=/", String.Empty);
The Set-Cookie header contains other related info about the cookie and possible other instructions for other cookies. I had one cookie with its info (Path), the session id which I needed to sent back to the server so the server would know that I am the same client which did the GET request.
The new request had to include this cookie
request.Headers["Cookie"] = phpsessid;
You already do this, but make sure the cookies you receive, you sent back to the server.
Considering session and authentication, there are two cookies, one for session, one for authentication and some servers/application might not allow to have an authentication without a valid session. What I want to say is that you might need to pass the session cookie too. So the steps would be:
Do first a GET request to obtain the session cookie.
Next do the POST request to authenticate and get the auth cookie.
Use both cookies to navigate to the protected pages.
Also check this question, it doesn't show the entire class, but the idea is to keep the CookieContainer in the same class, add the new cookies from POST/GET requests and assign them to the each new request, like #Krizz answered.
Try using CookieContainer which is a class to keep cookies context between several requests. You simply create an instance of it and assign it to each WebRequest.
Therefore, modifying your code:
string formParams = string.Format("j_username={0}&j_password={1}", userName, userPass);
string cookieHeader;
var cookies = new CookieContainer(); // added this line
var request = WebRequest.Create(_signInPage) as HttpWebRequest; // modified line
request.CookieContainer = cookies; // added this line
request.ContentType = "text/plain";
request.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes(formParams);
request.ContentLength = bytes.Length;
using (Stream os = request.GetRequestStream())
{
os.Write(bytes, 0, bytes.Length);
}
request.GetResponse(); // removed some code here, no need to read response manually
var getRequest = WebRequest.Create(sessionHistoryPage) as HttpWebRequest; //modified line
getRequest.CookieContainer = cookies; // added this line
getRequest.Method = "GET";
WebResponse getResponse = getRequest.GetResponse();
try
{
using (StreamReader sr = new StreamReader(getResponse.GetResponseStream()))
{
textBox1.AppendText(sr.ReadToEnd());
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}

Categories