Search code examples
c#oauthoauth-2.0bing-api

Bing Ads OAuth Automation using only .NET?


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;
            }

Solution

  • 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.