Bing Ads OAuth Automation using only .NET? - c#

How can I log onto Microsoft Live (with .NET WebClient?) and automate the OAuth process to get a token to make Bing Ads API calls?
My question is similar to How do I get an OAuth request_token from live.com?. However, I am building (C#, .NET 4.5.2) a headless Windows Service using the context of a Bing Ads super admin account that is linked to multiple other Bing Ads accounts. The idea is to authenticate, get the auth bits, and then make calls using the bits at 3:00am. Some of the accounts "compete" so for example group A should not see data from group B, so having an application get data for everyone and filter it and distribute it overnight solves many business problems.
I am concerned that if Live experiences problems, or our application is down for an extended time for any reason, we will have to re-authenticate manually to get data again. Maintenance and management of the credentials is now additional overhead (this is for an enterprise environment) that will have to take the form of an intranet web site/page to allow junior/uninitiated folks to do the work if needed (lets not forget testing and documentation). To contrast, Google provides an option to use key pairs for groups that need to work in a fully automated manner. It appears that Twitter's OAuth2 implementation can be automated without a GUI logon. It appears that other Bing services (eg Translation) can also be automated with WebClient.
I have the Microsoft account name and password already, and have a callback URL of "local-mydomain.com" set in the Bing Ads app GUI (and have a HOSTS entry for local-mydomain.com).
The Microsoft sample appears to work, but it automates the MS Web Browser Control, expects a user to input credentials in the GUI, and then the token is given. Giving the super admin account to users to do this is not an option. Expecting a user to get up at 3:00am to authenticate to upload/download data is not an option. Expecting a user to get desktop access to a server in the farm to "run something" is not an option.
All OAuth ideas appreciated.
Thanks.
Here is the launching code:
partial class OAuthForm : Form
{
private static OAuthForm _form;
private static WebBrowser _browser;
private static string _code;
private static string _error;
// When you register your application, the Client ID is provisioned.
private const string ClientId = "000redacted000";
// Request-related URIs that you use to get an authorization code,
// access token, and refresh token.
private const string AuthorizeUri = "https://login.live.com/oauth20_authorize.srf";
private const string TokenUri = "https://login.live.com/oauth20_token.srf";
private const string DesktopUri = "https://login.live.com/oauth20_desktop.srf";
private const string RedirectPath = "/oauth20_desktop.srf";
private const string ConsentUriFormatter = "{0}?client_id={1}&scope=bingads.manage&response_type=code&redirect_uri={2}";
private const string AccessUriFormatter = "{0}?client_id={1}&code={2}&grant_type=authorization_code&redirect_uri={3}";
private const string RefreshUriFormatter = "{0}?client_id={1}&grant_type=refresh_token&redirect_uri={2}&refresh_token={3}";
// Constructor
public OAuthForm(string uri)
{
InitializeForm(uri);
}
[STAThread]
static void Main()
{
// Create the URI to get user consent. Returns the authorization
// code that is used to get an access token and refresh token.
var uri = string.Format(ConsentUriFormatter, AuthorizeUri, ClientId, DesktopUri);
_form = new OAuthForm(uri);
// The value for "uri" is
// https://login.live.com/oauth20_authorize.srf?client_id=000redacted000&scope=bingads.manage&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf
_form.FormClosing += form_FormClosing;
_form.Size = new Size(420, 580);
Application.EnableVisualStyles();
// Launch the form and make an initial request for user consent.
// For example POST /oauth20_authorize.srf?
// client_id=<ClientId>
// &scope=bingads.manage
// &response_type=code
// &redirect_uri=https://login.live.com/oauth20_desktop.srf HTTP/1.1
Application.Run(_form); // <!---------- Problem is here.
// I do not want a web browser window to show,
// I need to automate the part of the process where
// a user enters their name/password and are
// redirected.
// While the application is running, browser_Navigated filters traffic to identify
// the redirect URI. The redirect's query string will contain either the authorization
// code if the user consented or an error if the user declined.
// For example https://login.live.com/oauth20_desktop.srf?code=<code>
// If the user did not give consent or the application was
// not registered, the authorization code will be null.
if (string.IsNullOrEmpty(_code))
{
Console.WriteLine(_error);
return;
}

Whatever you do, the "super admin" will have to log on at least once, using a browser. You can do that by hosting a simple web page in your service, or you could do it as part of the setup process. The Live samples show you how to do that.
Once the "super admin" has logged on using the code grant, you receive an access token and a refresh token. I'm not sure how long the Live access token is valid, but it is probably log enough for one nightly run. Save the refresh token in a safe place. The following night, you start by exchanging that refresh token by a new access token and a new refresh token. Again, you save this new refresh token for the following night.
You can keep this process running forever, as long as the "super admin" does not revoke the authorization he gave to your app.
UPDATE:
Some OAuth 2.0 servers support the "Resource Owner Password Credentials Grant", see the RFC at https://www.rfc-editor.org/rfc/rfc6749. If the Live server supports that, it would be an alternative to the Code Grant that does not require a browser. However, even of the server supports it, I would recommend against it for security reasons, as it requires storing your "super admin" password on the server. If someone grabs the password, they have full access to the account, and all resources protected by it. It will also break down if you change the password. The code grant does not have these problems.
Your question states that you want or need to run as this "super admin". An other option might be to use the "Client Credentials Grant". However, this also requires a client secret to be stored on the server (as with the password credentials grant). Furthermore, it still requires the super admin to authorize the client, and that in itself requires a code grant using a browser.
You ask why the code grant requires a browser, why you can not use some kind of screen scraping to simulate a browser interaction. First of all, you cannot predict the screens that will be shown to the user. These screens change without notice. More importantly, depending on user options and history, the server shows different screens. For example, the user may have turned on two-factor authentication. Last but not least, why do you object to opening a browser? It will probably be easier than trying to emulate it.
Finally, these "super admin" users might object to giving their password to your application, as they don't really know what you are doing with it (you might be sending to a server of your own, as far as they know). Using the Code Grant with a browser, they know your application never gets to see their password (kind of - you could listen in on browser events or something, unless the browser control is run in a separate process not under your control, such as the Windows 8 WebAuthenticationBroker). Your application only gets a token with the scopes they authorize.

After spending a few hours on this problem for myself and finding absolutely no solution to automate connecting to Bing from a service. Here is what will work using the wonderful WatiN
First grab WatiN and add it to your solution via Nuget.
Then use the following code (my example works in a console application as an Example) to automate the whole grabbing of a token from Microsoft. It's not perfect as this is a sample but it will work.
You should double check the element ID's I'm using in case they changed, they are hard coded - generally remove all the hard coding if your going to use this in a production environment.
I didn't want anyone else to have to go through this.
First it grabs a Code that is then used to grab a Token, just like the OAuth 2.0 specification requires.
using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using WatiN.Core.Native;
using WatiN.Core;
namespace LouiesOAuthCodeGrantFlow
{
// Using access tokens requires that you register your application and that
// the user gives consent to your application to access their data. This
// example uses a form and WebBrowser control to get the user's consent.
// The control and form require a single-threaded apartment.
partial class LouiesBingOAuthAutomation
{
private static LouiesBingOAuthAutomation _form;
private static string _code;
private static string _error;
//your going to want to put these in a secure place this is for the sample
public const string UserName = "your microsoft user name";
public const string Password = "<your microsoft account password";
// When you register your application, the Client ID is provisioned.
//get your clientid https://developers.bingads.microsoft.com/Account
private const string ClientId = "<your client id>";
// Request-related URIs that you use to get an authorization code,
// access token, and refresh token.
private const string AuthorizeUri = "https://login.live.com/oauth20_authorize.srf";
private const string TokenUri = "https://login.live.com/oauth20_token.srf";
private const string DesktopUri = "https://login.live.com/oauth20_desktop.srf";
private const string RedirectPath = "/oauth20_desktop.srf";
private const string ConsentUriFormatter = "{0}?client_id={1}&scope=bingads.manage&response_type=code&redirect_uri={2}";//&displayNone
private const string AccessUriFormatter = "{0}?client_id={1}&code={2}&grant_type=authorization_code&redirect_uri={3}";
private const string RefreshUriFormatter = "{0}?client_id={1}&grant_type=refresh_token&redirect_uri={2}&refresh_token={3}";
// Constructor
public LouiesBingOAuthAutomation(string uri)
{
InitializeForm(uri);
}
[STAThread]
static void Main()
{
var uri = string.Format(ConsentUriFormatter, AuthorizeUri, ClientId, DesktopUri);
_form = new LouiesBingOAuthAutomation(uri);
if (string.IsNullOrEmpty(_code))
{
Console.WriteLine(_error);
return;
}
uri = string.Format(AccessUriFormatter, TokenUri, ClientId, _code, DesktopUri);
AccessTokens tokens = GetAccessTokens(uri);
Console.WriteLine("Access token expires in {0} minutes: ", tokens.ExpiresIn / 60);
Console.WriteLine("\nAccess token: " + tokens.AccessToken);
Console.WriteLine("\nRefresh token: " + tokens.RefreshToken);
uri = string.Format(RefreshUriFormatter, TokenUri, ClientId, DesktopUri, tokens.RefreshToken);
tokens = GetAccessTokens(uri);
Console.WriteLine("Access token expires in {0} minutes: ", tokens.ExpiresIn / 60);
Console.WriteLine("\nAccess token: " + tokens.AccessToken);
Console.WriteLine("\nRefresh token: " + tokens.RefreshToken);
}
private void InitializeForm(string uri)
{
using (var browser = new IE(uri))
{
var page = browser.Page<MyPage>();
page.PasswordField.TypeText(Password);
try
{
StringBuilder js = new StringBuilder();
js.Append(#"var myTextField = document.getElementById('i0116');");
js.Append(#"myTextField.setAttribute('value', '"+ UserName + "');");
browser.RunScript(js.ToString());
var field = browser.ElementOfType<TextFieldExtended>("i0116");
field.TypeText(UserName);
}
catch (Exception ex)
{
Console.Write(ex.Message + ex.StackTrace);
}
page.LoginButton.Click();
browser.WaitForComplete();
browser.Button(Find.ById("idBtn_Accept")).Click();
var len = browser.Url.Length - 43;
string query = browser.Url.Substring(43, len);
if (query.Length == 50)
{
if (!string.IsNullOrEmpty(query))
{
Dictionary<string, string> parameters = ParseQueryString(query, new[] { '&', '?' });
if (parameters.ContainsKey("code"))
{
_code = parameters["code"];
}
else
{
_error = Uri.UnescapeDataString(parameters["error_description"]);
}
}
}
}
}
// Parses the URI query string. The query string contains a list of name-value pairs
// following the '?'. Each name-value pair is separated by an '&'.
private static Dictionary<string, string> ParseQueryString(string query, char[] delimiters)
{
var parameters = new Dictionary<string, string>();
string[] pairs = query.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
foreach (string pair in pairs)
{
string[] nameValue = pair.Split(new[] { '=' });
parameters.Add(nameValue[0], nameValue[1]);
}
return parameters;
}
// Gets an access token. Returns the access token, access token
// expiration, and refresh token.
private static AccessTokens GetAccessTokens(string uri)
{
var responseSerializer = new DataContractJsonSerializer(typeof(AccessTokens));
AccessTokens tokenResponse = null;
try
{
var realUri = new Uri(uri, UriKind.Absolute);
var addy = realUri.AbsoluteUri.Substring(0, realUri.AbsoluteUri.Length - realUri.Query.Length);
var request = (HttpWebRequest)WebRequest.Create(addy);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (var writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(realUri.Query.Substring(1));
}
var response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
if (responseStream != null)
tokenResponse = (AccessTokens)responseSerializer.ReadObject(responseStream);
}
}
catch (WebException e)
{
var response = (HttpWebResponse)e.Response;
Console.WriteLine("HTTP status code: " + response.StatusCode);
}
return tokenResponse;
}
}
public class MyPage : WatiN.Core.Page
{
public TextField PasswordField
{
get { return Document.TextField(Find.ByName("passwd")); }
}
public WatiN.Core.Button LoginButton
{
get { return Document.Button(Find.ById("idSIButton9")); }
}
}
[ElementTag("input", InputType = "text", Index = 0)]
[ElementTag("input", InputType = "password", Index = 1)]
[ElementTag("input", InputType = "textarea", Index = 2)]
[ElementTag("input", InputType = "hidden", Index = 3)]
[ElementTag("textarea", Index = 4)]
[ElementTag("input", InputType = "email", Index = 5)]
[ElementTag("input", InputType = "url", Index = 6)]
[ElementTag("input", InputType = "number", Index = 7)]
[ElementTag("input", InputType = "range", Index = 8)]
[ElementTag("input", InputType = "search", Index = 9)]
[ElementTag("input", InputType = "color", Index = 10)]
public class TextFieldExtended : TextField
{
public TextFieldExtended(DomContainer domContainer, INativeElement element)
: base(domContainer, element)
{
}
public TextFieldExtended(DomContainer domContainer, ElementFinder finder)
: base(domContainer, finder)
{
}
public static void Register()
{
Type typeToRegister = typeof(TextFieldExtended);
ElementFactory.RegisterElementType(typeToRegister);
}
}
// The grant flow returns more fields than captured in this sample.
// Additional fields are not relevant for calling Bing Ads APIs or refreshing the token.
[DataContract]
class AccessTokens
{
[DataMember]
// Indicates the duration in seconds until the access token will expire.
internal int expires_in = 0;
[DataMember]
// When calling Bing Ads service operations, the access token is used as
// the AuthenticationToken header element.
internal string access_token = null;
[DataMember]
// May be used to get a new access token with a fresh expiration duration.
internal string refresh_token = null;
public string AccessToken { get { return access_token; } }
public int ExpiresIn { get { return expires_in; } }
public string RefreshToken { get { return refresh_token; } }
}
}

Related

Need to Access Google Drive V3 via IIS application - Without Using MVC

My need is very specific. I need to access a directory on Google Drive that is a shared folder. The only thing in it will be empty form documents and spreadsheet templates. There is nothing of value in the folder, and it is used internally only. So, I can be very optimistic WRT security concerns. I just need access.
I am extending an existing ERP system that runs as an IIS application.
My customization is .NET/C# project that extends the ERP's .NET classes. I cannot implement a login/auth system because one already exists for the ERP.
I did the .NET quickstart, but of course that is a console app, and will not work when I move it to IIS. The suggestion to follow the standard MVC model doesn't work for me -- adding a second web site/page is needlessly complicated for my needs.
My question is: How can I authorize access to a Google Drive that
A) Runs within IIS
B) Does not require a separate ASP Web Application to implement MVC for authorization.
=============================
Similar to issues in:
Google API Fails after hosting it to IIS
you could use OAuth authorization with your asp.net application:
Create Web Server client_secret.json.by using GetAuthorizationUrl() create url for get toke temporary token.Redirect to GoogleCallback() and get refresh and access tokens using ExchangeAuthorizationCode().Save them to the file "~/Resources/driveApiCredentials/drive-credentials.json/Google.Apis.Auth.OAuth2.Responses.TokenResponse-{account}".Use this saved tokens.
you could refer to the below link for more detail:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Google Drive API upload Fails after hosting it to IIS. showing the error as Failed to launch the Browser with
Google Drive API not uploading file from IIS
Google Data API Authorization Redirect URI Mismatch
Jalpa's answer was not what I was looking for, nor was anything referenced in any of the links.
I'm going to put my answer here, because it is what I needed, and it might be useful to others.
First the overview
Google's QuickStart for .NET only shows the console based solution. As many have discovered, this does not work when you switch to an IIS based solution. It is almost as if the API deliberately defeats your attempts to do so. It simply will not allow you to use a token created for a local application using GoogleWebAuthorizationBroker.AuthorizeAsync -- it will error even if a browser isn't needed. (ie the token hasn't expired, so it won't need the browser to authenticate anything.)
Trying to run a refresh authorization gives you a token, but not a service. And even if the token is valid, you still can't use AuthorizeAsync to get your service from an IIS application (see above)
This is how I handle this:
Do the quick start and run the authorization that pops up the local browser and allows you to login and authenticate.
It creates a local folder(token.json), where it puts a token file (Google.Apis.Auth.OAuth2.Responses.TokenResponse-user) It's just a json file. Open it in notepad++ and you will find the fields:
"access_token": "token_type": "expires_in": "refresh_token":
"scope": "Issued": "IssuedUtc":
You need the refresh_token. I simply combined that with the initial credentials file I downloaded from the Google API Console (i.e. "credentials.json") and named it "skeleton_key.json"
This file is all you will need to generate valid tokens forever.
I have 2 classes I use for this. First the class that creates the Drive Service:
public class GDriveClass
{
public String LastErrorMessage { get; set; }
static string[] Scopes = { DriveService.Scope.Drive }; // could pull this from skeleton_key
static string ApplicationName = "GDrive Access"; // this is functionally irrelevant
internal UserCredential usrCredentials;
internal Google.Apis.Drive.v3.DriveService CurrentGDriveService = null;
internal String basePath = "."; // this comes in from calling program
// which uses HttpContext.Current.Server.MapPath("~");
public GDriveClass(string logFileBasePath)
{
basePath = logFileBasePath;
LastErrorMessage = "";
}
#region Google Drive Authenticate Code
public bool AuthenticateUser(string FullTokenAccessFileName)
{
UserCredential credential;
String JsonCredentialsonFile = System.IO.File.ReadAllText(FullTokenAccessFileName);
string credPath = basePath + #"\Credentials\token.json";
// Force a Refresh of the Token
RefreshTokenClass RTC = new RefreshTokenClass();
// Set field values in RefreshTokenClass:
var jObject = Newtonsoft.Json.Linq.JObject.Parse(JsonCredentialsonFile);
var fieldStrings = jObject.GetValue("installed").ToString();
var fields = Newtonsoft.Json.Linq.JObject.Parse(fieldStrings);
RTC.client_id = fields.GetValue("client_id").ToString();
RTC.client_secret = fields.GetValue("client_secret").ToString();
RTC.refresh_token = fields.GetValue("refresh_token").ToString();
RTC.ExecuteRefresh(); // this gets us a valid token every time
try
{
GoogleCredential gCredentials = GoogleCredential.FromAccessToken(RTC.access_token);
CurrentGDriveService = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = gCredentials,
ApplicationName = ApplicationName,
});
return true;
}
catch (Exception ex)
{
LastErrorMessage = "Error: Authenticating - " + ex.Message;
return false;
}
}
Usage is pretty straight forward:
string TokenFile = #basePath + #"\skeleton_key.json";
GDRIVER.AuthenticateUser(TokenFile);
var rslt = GDRIVER.LastErrorMessage;
if (!String.IsNullOrEmpty(rslt))
{
WriteToLogFile("ERROR in Google AuthenticateUser() ");
AlertMessage("Unable To Connect to Google Drive - Authorization Failed");
return;
}
And this is the class that refreshes the token via REST API as needed:
public class RefreshTokenClass
{
public string application_name { get; set; }
public string token_source { get; set; }
public string client_id { get; set; }
public string client_secret { get; set; }
public string scope { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public RefreshTokenClass()
{
}
public bool ExecuteRefresh()
{
try
{
RestClient restClient = new RestClient();
RestRequest request = new RestRequest();
request.AddQueryParameter("client_id", this.client_id);
request.AddQueryParameter("client_secret", this.client_secret);
request.AddQueryParameter("grant_type", "refresh_token");
request.AddQueryParameter("refresh_token", this.refresh_token);
restClient.BaseUrl = new System.Uri("https://oauth2.googleapis.com/token");
var restResponse = restClient.Post(request);
// Extracting output data from received response
string response = restResponse.Content.ToLower(); // make sure case isn't an issue
// Parsing JSON content into element-node JObject
var jObject = Newtonsoft.Json.Linq.JObject.Parse(restResponse.Content);
//Extracting Node element using Getvalue method
string _access_token = jObject.GetValue("access_token").ToString();
this.access_token = _access_token;
return true;
}
catch (Exception ex)
{
//Console.WriteLine("Error on Token Refresh" + ex.Message);
return false;
}
}
Note: This makes use of Newtonsoft.Json and RestSharp.
Thanks to user: "OL." who gave me the way of creating a service from a token (that somehow I missed in the docs!)
How to create Service from Access Token
And to user:"purshotam sah" for a clean REST API approach
Generate Access Token Using Refresh Token

Authentication to ASP.NET Web Api using ADFS

I am in the situation that I need to access a ASP.NET Web Api that is using ADFS for authentication. I can hit it reliably through my browser by going through the ADFS login portal and getting the relevant FedAuth cookie. Unfortunately I need to access it from outside of a dedicated browser for use in a mobile app. The project is pretty much a slightly modified version of the standard visual studio web api template set up for Work and School Authentication (on-premises) and set up for cookie authentication.
bit of code from Startup.Auth.cs:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
});
}
I can't seem to figure out where to start. I've tried requesting a access token from the ADFS and can get different versions of SAML assertions using relevant login info, but it gets rejected by the web API. Have I misunderstood how it's supposed to work?
From my understanding it's supposed to go like this:
How I think it's supposed to work
App requests a authentication token from the ADFS
ADFS gives the requestee an auth token if the information provided was correct
App makes request to the web API and sending the token along inside a cookie called FedAuth(by default anyway) as a base64 encoded string
Web Api sends the token to the ADFS to find out if the token is correct.
ADFS responds with some sort of success message
Web Api responds to the app either with a rejection or a piece of data depending on how authentication went.
This is what I have right now while trying to figure out how to get a hold of the correct tokens.
using System;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.Net;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Security;
using Thinktecture.IdentityModel.Extensions;
using Thinktecture.IdentityModel.WSTrust;
namespace ConsoleApplication1
{
class Program
{
private const string UserName = "USERNAME";
private const string Password = "PASSWORD";
private const string Domain = "DOMAIN";
private const string ADFSEndpoint = "ADFS ENDPOINT";
private const string ApiBaseUri = "THE API";
private const string ApiEndPoint = "AN ENDPOINT";
static void Main(string[] args)
{
SecurityToken token = RequestSecurityToken(); // Obtain security token from ADFS.
CallApi(token); // Call api.
Console.ReadKey(); // Stop console from closing
}
private static SecurityToken RequestSecurityToken()
{
var trustChannelFactory =
new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(new Uri(ADFSEndpoint)))
{
TrustVersion = TrustVersion.WSTrust13,
Credentials = { UserName = { UserName = UserName + "#" + Domain, Password = Password } },
};
var requestSecurityToken = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new EndpointReference(ApiBaseUri)
};
RequestSecurityTokenResponse response;
var securityToken = trustChannelFactory.CreateChannel().Issue(requestSecurityToken, out response);
return securityToken;
}
private static async void CallApi(SecurityToken securityToken)
{
using (var handler = new HttpClientHandler { CookieContainer = new CookieContainer() })
{
using (var client = new HttpClient(handler))
{
handler.CookieContainer.MaxCookieSize = 8000; // Trying to make sure I can fit it in the cookie
var cookie = new Cookie {
Name = "FedAuth",
Value = Base64Encode(securityToken.ToTokenXmlString()),
HttpOnly = true,
Secure = true
};
handler.CookieContainer.Add(new Uri(ApiBaseUri), cookie);
var response = client.GetAsync(new Uri(ApiBaseUri + ApiEndPoint)).Result;
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
}
}
I can't quite remember what code I based my example of, but if anyone can point me in the right direction or tell me where I fucked up I'd appreciate it.
Edit: Sorry, forgot to add what I am getting.
The Web Api vomits out a bunch of debug information because an exception was thrown, telling me that a SecurityContextToken is expected instead of a saml:Assertion that I am apparently getting. Maybe my googlefoo is not powerful enough, but I can't seem to figure out where to start with this. Can I setup the api to accept SAML assertions or do I need to request the token in a different way?
You can't use WS-Fed to call a web API. You need OpenID Connect / OAuth as in Calling a web API in a web app using Azure AD and OpenID Connect.
It's for Azure AD but it does illustrate the flow.
What version of ADFS?
If 2.0, there is no OAuth support.
If 3.0, web API only - refer Securing a Web API with ADFS on WS2012 R2 Got Even Easier.
If 4.0, you have the full stack.

Twitch API Call from C# streams/followed

I had developed my own C# application to make calls to twitch api to get list of all the live users i am currently following. It was working fine until April or something.
I pass this string to the call in the code via WebClient.DownloadString to be parsed with json:
https://api.twitch.tv/kraken/streams/followed?oauth_token=[mytoken]&stream_type=live
This used to work fine and returned a long parsable string from web which i used in my app.
But now the string returned says that i am missing the scope.(my oauth is correct)
So i went on and tried this on a web browser to check if my oauth is correct via this link: https://api.twitch.tv/kraken?oauth_token=[my oauth]
This returns a valid string but;
It turns out that the token i got from web has only chat_login scope. I want to set a user_read scope or at least retrieve a token which has user_read scope. Not chat_login scope; as chat_login scope does not permit me getting string via https//api.twitch.tv/kraken/streams/followed?oauth_token=[mytoken]&stream_type=live
In the authentication manual of twitch api in github there is a process which requires a call that grants my application access to my twitch info. I need to specify redirect_uri there to get the access_token which has the user_read scope.
But i have no idea about how to specify a redirect_uri and which function to call that will return the access_token string with the user_read scope that i need.
Is this new in v3 twitch api? (Which is probably changed around in April?)
To summarize: What should i do to get an #access_token which has a user_read scope rather than chat_login scope?
My Code:
public partial class Form1 : Form
{
const string chName = "ryetta";
const string FollowsURL = "https://api.twitch.tv/kraken/users/" + chName + "/follows/channels?limit=200&sortby=last_broadcast";
const string oauth = "4yk...";
const string FollowedURL = "https://api.twitch.tv/kraken/streams/followed?oauth_token=" + oauth + "&stream_type=live";
BindingList<ChannelData> channels = new BindingList<ChannelData>();
WebClient wc;
string selectedChannelName;
public Form1()
{
InitializeComponent();
wc = new WebClient();
}
private void button1_Click(object sender, EventArgs e)
{
string s = jsonget(FollowedURL); // this is where i get the scope error so the other parts of the code is irrelevant
//JObject jObj = JObject.Parse(s);
StreamFollowed streams1 = JsonConvert.DeserializeObject<StreamFollowed>(s);
channels.Clear();
foreach (Stream stream in streams1.streams)
{
System.IO.Stream str = wc.OpenRead(stream.preview.medium);
Bitmap bmp = new Bitmap(str);
channels.Add(new ChannelData(stream, bmp));
}
dataGridView1.DataSource = channels.Select(cdata => new {
cdata.Name,
cdata.ViewerCount,
cdata.Game,
cdata.Title,
cdata.UpTimeString,
cdata.BmpPreview,
cdata.followersCount,
cdata.ratio}).ToList();
selectedChannelName = (string)dataGridView1.SelectedRows[0].Cells[0].Value;
}
public string jsonget(string url)
{
return wc.DownloadString(url);
}
Thanks in advance for your time.
I'm not sure if this is correct or not but the URL you use for the api call I think has changed
https//api.twitch.tv/kraken/streams/followed?oauth_token=[mytoken]&stream_type=live
https//api.twitch.tv/kraken/channels/followed?oauth_token=[mytoken]&stream_type=live
I think they changed it from streams to channels
Sorry if this doesn't help though

Get access token to facebook page - WPF

I am developing a WPF application that needs post on wall of a facebook's Page, and this without login window.
Well, I want to get access token for my facebook page, and this is my code.
var fb = new FacebookClient();
string token = "";
dynamic accounts = fb.Get("/"<USER_ID>"/accounts");
foreach (dynamic account in accounts)
{
if (account.id == <PAGE_ID>)
{
token = account.access_token;
break;
}
}
But I receive a error #104. It is a simple error, that I need a access token to do this operation.
Then I use other code to get the user access token
var fb = new FacebookClient();
dynamic result = fb.Get("oauth/access_token", new
{
client_id = <PAGE_ID>,
client_secret = <APP_SECRET>,
grant_type = "fb_exchange_token",
fb_exchange_token = <USER_TOKEN>
});
But I get error #101:
"Error validating application. Cannot get application info due to a system error."
Someone knows what I have to do?
Thanks!!!
I'm not sure if you've been able to get a never expiring token for the page, so I'll explain you the steps:
Open Graph API Explorer
Select your app from the drop-down
Click "Get Access Token" button, and select the manage_pages permission.
Copy the token and run this in the browser:
https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={app-id}&client_secret={app-secret}&fb_exchange_token={step-3-token}
Copy the token from step-4 and paste in to the access_token field and call:
/{page-id}?fields=access_token
The token you get now is a never-expiring token, you can validate the same in Debugger .Use this in your app.
But beware, its not recommended to use this token on client side if your app is public.
If you use the fb_exchange_token call, it will give you a token that expires after 60 days. In order to make it work correctly, I had to go through the login web flow to guarantee I got an up-to-date page access token.
Go to the Facebook App dashboard
If you haven't already added the Facebook Login product, click "+ Add Product" and select Facebook Login
Enable the "embedded browser control" option and enter https://www.facebook.com/connect/login_success.html as the allowed redirect URL.
Make a Window with a WebView control on it. The WebBrowser control no longer works; the browser engine powering it is too old.
Add code to listen for the navigation to the success URL:
this.webView.NavigationCompleted += (sender, args) =>
{
if (args.Uri.AbsolutePath == "/connect/login_success.html")
{
if (args.Uri.Query.Contains("error"))
{
MessageBox.Show("Error logging in.");
}
else
{
string fragment = args.Uri.Fragment;
var collection = HttpUtility.ParseQueryString(fragment.Substring(1));
string token = collection["access_token"];
// Save the token somewhere to give back to your code
}
this.Close();
}
};
Add code to navigate to the facebook login URL:
string returnUrl = WebUtility.UrlEncode("https://www.facebook.com/connect/login_success.html");
this.webView.Source = new Uri($"https://www.facebook.com/dialog/oauth?client_id={appId}&redirect_uri={returnUrl}&response_type=token%2Cgranted_scopes&scope=manage_pages&display=popup");
Call window.ShowDialog() to pop up the login window, then grab the user access token.
Create some models to help you out:
public class AccountsResult
{
public List<Account> data { get; set; }
}
public class Account
{
public string access_token { get; set; }
public string id { get; set; }
}
Using the user access token, get the page access token:
FacebookClient userFacebookClient = new FacebookClient(userAccessToken);
var accountsResult = await userFacebookClient.GetTaskAsync<AccountsResult>("/me/accounts");
string pageAccessToken = accountsResult.data.FirstOrDefault(account => account.id == PageId)?.access_token;
if (pageAccessToken == null)
{
MessageBox.Show("Could not find page under user accounts.");
}
else
{
FacebookClient pageFacebookClient = new FacebookClient(pageAccessToken);
// Use pageFacebookClient here
}

Imgur API version 3 uploading examples?

I'm trying to add this functionality to a C# Windows app I have in development, for uploading images to Imgur.
Unfortunately it has to be Imgur, as that site is the requirement.
The problem is that whatever C# example code I can find is old and doesn't seem to work with their version 3 API.
So I was wondering if anyone with expertise in the area can help me out.
I would prefer to upload using OAuth, and not the Anonymous option, but the latter can be used as an example if needed.
EDIT:
One part I especially don't get is how can I make the authorization step happen while remaining within the desktop application. The authorization step requires the opening of a webpage, where the user is asked if they will allow the application to use their data or not.
How can I do this for a Desktop-based app?
Before you start you´ll need to register your Application to recieve a clientID and the client secret. Guess you´re aware of that already. Detailed information can be found on the official Imgur API Documentation.
Regarding authentication you´re right, the user has to visit a webpage and grab and authorize your appication there. You can either embed some Webbrowser control in your Application or simply instruct the user to browse to a webpage.
Here´s some untested Code that should work with little modifications.
class Program
{
const string ClientId = "abcdef123";
const string ClientSecret = "Secret";
static void Main(string[] args)
{
string Pin = GetPin(ClientId, ClientSecret);
string Tokens = GetToken(ClientId, ClientSecret, Pin);
// Updoad Images or whatever :)
}
public static string GetPin(string clientId, string clientSecret)
{
string OAuthUrlTemplate = "https://api.imgur.com/oauth2/authorize?client_id={0}&response_type={1}&state={2}";
string RequestUrl = String.Format(OAuthUrlTemplate, clientId, "pin", "whatever");
string Pin = String.Empty;
// Promt the user to browse to that URL or show the Webpage in your application
// ...
return Pin;
}
public static ImgurToken GetToken(string clientId, string clientSecret, string pin)
{
string Url = "https://api.imgur.com/oauth2/token/";
string DataTemplate = "client_id={0}&client_secret={1}&grant_type=pin&pin={2}";
string Data = String.Format(DataTemplate, clientId, clientSecret, pin);
using(WebClient Client = new WebClient())
{
string ApiResponse = Client.UploadString(Url, Data);
// Use some random JSON Parser, you´ll get access_token and refresh_token
var Deserializer = new JavaScriptSerializer();
var Response = Deserializer.DeserializeObject(ApiResponse) as Dictionary<string, object>;
return new ImgurToken()
{
AccessToken = Convert.ToString(Response["access_token"]),
RefreshToken = Convert.ToString(Response["refresh_token"])
};
}
}
}
public class ImgurToken
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}

Categories

Resources