4

I'm trying to access a Purchase Status API from my ASP.NET web server using Google APIs .NET Client Library which is a recommended way for using Purchase API v1.1. However, the Authorization page of this API suggests direct web requests to Google's OAuth2 pages instead of using the corresponding client libraries.

OK, I tried both methods with all variations I could imagine and both of them lead to "The remote server returned an error: (400) Bad Request.".

Now what I've done to get to my point. First I've made all steps 1-8 under the Creating an APIs Console project of the Authorization page. Next I generated a refresh token as described there. During refresh token generation I chose the same Google account as I used to publish my Android application (which is in published beta state now).

Next I've created a console C# application for test purposes in Visual Studio (may be console app is the problem?) and tried to call the Purchase API using this code (found in some Google API examples):

    private static void Main(string[] args)
    {
        var provider =
            new WebServerClient(GoogleAuthenticationServer.Description)
                {
                    ClientIdentifier = "91....751.apps.googleusercontent.com",
                    ClientSecret = "wRT0Kf_b....ow"
                };
        var auth = new OAuth2Authenticator<WebServerClient>(
            provider, GetAuthorization);

        var service = new AndroidPublisherService(
            new BaseClientService.Initializer()
                {
                    Authenticator = auth,
                    ApplicationName = APP_NAME
                });

        var request = service.Inapppurchases.Get(
            PACKAGE_NAME, PRODUCT_ID, PURCHASE_TOKEN);
        var purchaseState = request.Execute();

        Console.WriteLine(JsonConvert.SerializeObject(purchaseState));
    }

    private static IAuthorizationState GetAuthorization(WebServerClient client)
    {
        IAuthorizationState state =
            new AuthorizationState(
                new[] {"https://www.googleapis.com/auth/androidpublisher"})
                {
                    RefreshToken = "4/lWX1B3nU0_Ya....gAI"
                };

        // below is my redirect URI which I used to get a refresh token
        // I tried with and without this statement
        state.Callback = new Uri("https://XXXXX.com/oauth2callback/");

        client.RefreshToken(state); // <-- Here we have (400) Bad request
        return state;
    }

Then I tried this code to get the access token (I found it here: Google Calendar API - Bad Request (400) Trying To Swap Code For Access Token):

    public static string GetAccessToken()
    {
        var request = WebRequest.Create(
            "https://accounts.google.com/o/oauth2/token");
        request.Method = "POST";
        var postData =
            string.Format(
                @"code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type=authorization_code",
            // refresh token I got from browser
            // also tried with Url encoded value
            // 4%2FlWX1B3nU0_Yax....gAI
                "4/lWX1B3nU0_Yax....gAI",
            // ClientID from Google APIs Console
                "919....1.apps.googleusercontent.com",
            // Client secret from Google APIs Console
                "wRT0Kf_bE....w",
            // redirect URI from Google APIs Console
            // also tried Url encoded value
            // https%3A%2F%2FXXXXX.com%2Foauth2callback%2F
                "https://XXXXX.com/oauth2callback/");

        byte[] byteArray = Encoding.UTF8.GetBytes(postData);
        request.ContentType = "application/x-www-form-urlencoded";
        request.ContentLength = byteArray.Length;
        using (var dataStream = request.GetRequestStream())
        {
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();
        }
        try
        {
            // request.GetResponse() --> (400) Bad request again!
            using (var response = request.GetResponse())
            {
                using (var dataStream = response.GetResponseStream())
                {
                    using (var reader = new StreamReader(dataStream))
                    {
                        var responseFromServer = reader.ReadToEnd();
                        var jsonResponse = JsonConvert.DeserializeObject<OAuth2Response>(responseFromServer);
                        return jsonResponse.access_token;
                    }
                }
            }
        }
        catch (Exception ex) { var x = ex; }
        return null;
    }

So, to sum up all my long story:

  1. Is it possible at all to pass OAuth2 authorization using either of methods above from a C# Console Application (without user interaction)?
  2. I've double checked the redirect URI (since I saw a lot of discussed troubles because of it here on stackoverflow) and other parameters like ClientID and ClientSecret. What else I could do wrong in the code above?
  3. Do I need to URL encode a slash in the refresh token (I saw that the first method using client library does it)?
  4. What is the recommended way of achieving my final goal (Purchase API access from ASP.NET web server)?
Community
  • 1
  • 1
IPSUS
  • 499
  • 4
  • 15
  • Why are you trying to pass the refresh token? All our samples use the regular OAuth2 flows to get access token and refresh token. Take a look in our samples repository (you can check the drive sample - https://code.google.com/p/google-api-dotnet-client/source/browse/?repo=samples#hg%2FDrive.Sample). In that scenario the .NET client library will set the Authorization header for you for each request (after the library exchanged the code with an access token). Please try using the OAuth2 library as mentioned in all of our samples. – peleyal Jul 29 '13 at 08:12
  • @peleyal These examples were my starting point. However, Google Drive example uses `AuthorizationMgr` class which under the hood uses `WindowTitleNativeAuthorizationFlow` and `LoopbackServerAuthorizationFlow` classes to perform OAuth tasks. Both of them seem not suitable for services since `WindowTitleNativeAuthorizationFlow` calls `OAuth2AuthorizationDialog.ShowDialog(url)` and `LoopbackServerAuthorizationFlow` calls `Process.Start(authUrl.ToString())`. And I need to perform OAuth authorization **without** user interaction in the context of ASP.NET process. – IPSUS Jul 29 '13 at 12:09

2 Answers2

15

I'll try to answer your last question. If you access your own data account, you dont need to use client id in oAuth2. Let's use service account to access Google Play API.

  1. Create a service account in Google Developer Console > Your project > APIs and auth > Credentials > Create a new key. You will download a p12 key.
  2. Create a C# project. You can choose console application.
  3. Install google play api library from Google.Apis.androidpublisher. Nuget. You can find other library for dotnet in Google APIs Client Library for .NET
  4. Link google api project with your google play account in API access

  5. Authenticate and try to query information. I'll try with listing all inapp item. You can just change to get purchase's status

    String serviceAccountEmail = "your-mail-in-developer-console@developer.gserviceaccount.com";
    
        var certificate = new X509Certificate2(@"physical-path-to-your-key\key.p12", "notasecret", X509KeyStorageFlags.Exportable);
    
        ServiceAccountCredential credential = new ServiceAccountCredential(
           new ServiceAccountCredential.Initializer(serviceAccountEmail)
           {
               Scopes = new[] { "https://www.googleapis.com/auth/androidpublisher" }
           }.FromCertificate(certificate));
    
    
        var service = new AndroidPublisherService(
       new BaseClientService.Initializer()
       {
           HttpClientInitializer = credential,
           ApplicationName = "GooglePlay API Sample",
       });
    // try catch this function because if you input wrong params ( wrong token) google will return error.
        var request = service.Inappproducts.List("your-package-name");
        var purchaseState = request.Execute();
    
       // var request = service.Purchases.Products.Get(
       //"your-package-name", "your-inapp-item-id", "purchase-token"); get purchase'status
    
    
    
        Console.WriteLine(JsonConvert.SerializeObject(purchaseState));
    
Trung Nguyen
  • 7,442
  • 2
  • 45
  • 87
  • 2
    Among the general confusion about Play API access and its many unsettled problems, your outstandig answer provides a working step-by-step guide and a simplistic piece of code. Thank you for it! – dakab Oct 01 '15 at 13:41
  • 1
    You really solved the problem! Thanks a lot for sharing your solution. A lot of people keep creating entropy around this subject but the one you suggested is so far the best solution I found. – bre_dev Apr 28 '16 at 18:51
  • this answer + this: http://stackoverflow.com/a/41454381/2293226 helped solve my problem – g2server Feb 09 '17 at 07:42
  • This works, but I am only getting 400 Invalid Value as response for all my tokens. – Danjoa Jan 04 '18 at 15:46
  • it's even possible to use a JSON key in place of a P12 certificate. `var credential = GoogleCredential.FromFile(@"api-1234.json").CreateScoped(AndroidPublisherService.Scope.Androidpublisher);` – Fabio Iotti Apr 19 '20 at 10:47
-1

You should do the following in your private static IAuthorizationState GetAuthorization(WebServerClient client) method:

        private IAuthorizationState GetAuthorization(WebServerClient client)
        {
            IAuthorizationState state = AuthState;
            if (state != null)
            {
                return state;
            }

            state = new AuthorizationState()
            {
                RefreshToken = "4/lWX1B3nU0_Ya....gAI",
                Callback = new Uri(@"https://XXXXX.com/oauth2callback/")
            };
            client.RefreshToken(state);

            // Store and return the credentials.
            HttpContext.Current.Session["AUTH_STATE"] = _state = state;
            return state;
        }
  1. Let me know if it works for you.
  2. Be aware that we know that the whole OAuth2 flow is awkward today, and we are working to improve it.
peleyal
  • 3,472
  • 1
  • 14
  • 25
  • If `AuthState` from the first statement is something that reads a previously saved IAuthorizationState than I see no substantial differences between your code and mine except you omitted the scope for `AuthorizationState` constructor and specified the `Callback` in the class initializer instead of using the property. I've updated all client libraries using NuGet package manager and the result is unfortunately the same - 400. If you are a part of Google dev team I can PM you my actual values or even send you a complete Visual Studio solution to check it out. – IPSUS Aug 09 '13 at 18:39
  • The 400 response is to your refresh token request or to getting your purchases? Can you attach the fiddler output of the requests? (without your actual client_id, secrets and refresh_token values – peleyal Aug 09 '13 at 19:29
  • Yes, I'm getting the response {"error" : "invalid_grant"} (400) to my refresh token request. Fiddler shows the request as follows: POST https://accounts.google.com/o/oauth2/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded; charset=utf-8 User-Agent: DotNetOpenAuth/4.0.0.11165 Host: accounts.google.com Cache-Control: no-store,no-cache Pragma: no-cache Content-Length: 191 Expect: 100-continue Connection: Keep-Alive refresh_token=4%2FlWX1B3...AI&grant_type=refresh_token&client_id=9...1.apps.googleusercontent.com&client_secret=wRT...Yow – IPSUS Aug 11 '13 at 11:06
  • The code I attached (which looks very similar to yours, worked for me). I'll continue to investigate this one. Did you try to constructs the AuthorizationState without scopes like I did? – peleyal Aug 11 '13 at 20:13