Server side authorization with Google Play Developer API?
Asked Answered
S

2

8

Authorization is required to fetch information from the Google Play Developer API.

I know how to do this with Postman, but implementing authorization is much more cumbersome (redirect url, handling redirects, and so on...) These would be the steps when you already have setup the auth data inside the Google Developer API Console.

1.) GET https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=http://www.myurl.com/oauth2callback&client_id=1234567890.apps.googleusercontent.com
2.) get code which was sent to redirect url. 
3.) POST https://accounts.google.com/o/oauth2/token
with
    grant_type:authorization_code
    code:[the code I got before]
    client_id:1234567890.apps.googleusercontent.com
    client_secret:[my client secret]
4.) Invoke GET https://www.googleapis.com/androidpublisher/v2/applications/packageName/purchases/subscriptions/subscriptionId/tokens/token
with:
  Scope: https://www.googleapis.com/auth/androidpublisher
and:
  access_token as query parameter I got before.

Now I want to do all this programmatically. Obviously not so easy. I thought the Google API Client Libraries will help, but I don't see, how these lib can help me with my use case.
For example classes like GoogleAuthorizationCodeFlow expect a user id at the moment of the request, but I not necessarily have one at this moment, so I wonder how this API should be used in a clean way.

Is there a clean way to handle OAuth2.0 easier / programmatically with some API to access Google Play Developer API? Otherwise I must implement it manually.

Static answered 9/1, 2018 at 20:28 Comment(3)
This is a really broad question.Marje
@Marje No it's not. I'm sure that the solution is a combination of some API classes. Any I guess I'm not the only one with this problem.Static
Requests for library recommendations, or for sample code are both considered off-topic. Asking how to use an API can be on topic.Marje
S
17

After lots of headache (like always with Google APIs and services) I figured out how one can access Google Play Developer API information (like billing) by using existing APIs.

1.) Create in Developer API Console a service account (JSON) key: enter image description here

2.) Download this service-account-private-key.json file (don't mistake it with the OAuth2.0 client secret file!).

3.) In Google Play Developer Console go to Settings -> Users & Permissions -> Invite New User and set as user e-mail of the new user the client_email from the downloaded file. Assign the access rights you want to give to this users via the checkboxes inside this view (for example 'View financial data').

4.) Add the proper dependency to your project (version ...-1.23.0 does not work for me):

<dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-androidpublisher</artifactId>
    <version>v2-rev50-1.22.0</version>
</dependency>

5.) Load the service-account-private-key.json file into your application. In my case it's a webserver:

@Singleton
@Startup
public class WebserverConfiguration
{
    private String serviceAccountPrivateKeyFilePath;

    /** Global instance of the HTTP transport. */
    public static HttpTransport HTTP_TRANSPORT;

    /** Global instance of the JSON factory. */
    public static JsonFactory JSON_FACTORY;

    private GoogleCredential credential;

    @PostConstruct
    public void init()
    {
        assignServiceAccountFileProperty();
        initGoogleCredentials();
    }

    public String getServiceAccountPrivateKeyFilePath()
    {
        return serviceAccountPrivateKeyFilePath;
    }

    public GoogleCredential getCredential()
    {
        return credential;
    }

    private void initGoogleCredentials()
    {
        try
        {
            newTrustedTransport();
            newJsonFactory();

            String serviceAccountContent = new String(Files.readAllBytes(Paths.get(getServiceAccountPrivateKeyFilePath())));
            InputStream inputStream = new ByteArrayInputStream(serviceAccountContent.getBytes());

            credential = GoogleCredential.fromStream(inputStream).createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));

        }
        catch (IOException | GeneralSecurityException e)
        {
            throw new InitializationException(e);
        }
    }

    private void newJsonFactory()
    {
        JSON_FACTORY = JacksonFactory.getDefaultInstance();
    }

    private void assignServiceAccountFileProperty()
    {
        serviceAccountPrivateKeyFilePath = System.getProperty("service.account.file.path");
        if (serviceAccountPrivateKeyFilePath == null)
        {
            throw new IllegalArgumentException("service.account.file.path UNKNOWN - configure it as VM startup parameter in Wildfly");
        }
    }

    private static void newTrustedTransport() throws GeneralSecurityException, IOException
    {
        if (HTTP_TRANSPORT == null)
        {
            HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        }
    }
}

6.) Now I am able the fetch Google Play Developer API information, e.g. reviews:

private void invokeGoogleApi() throws IOException
{       
    AndroidPublisher publisher = new AndroidPublisher.Builder(WebserverConfiguration.HTTP_TRANSPORT, WebserverConfiguration.JSON_FACTORY, configuration.getCredential()).setApplicationName("The name of my app on Google Play").build();
    AndroidPublisher.Reviews reviews = publisher.reviews();
    ReviewsListResponse reviewsListResponse = reviews.list("the.packagename.of.my.app").execute();
    logger.info("review list response = " + reviewsListResponse.toPrettyString());
}

This worked.

I cannot test it yet, but I'm sure that fetching the billing information works as well:

private SubscriptionPurchase getPurchase() throws IOException
{
    AndroidPublisher publisher = new AndroidPublisher.Builder(WebserverConfiguration.HTTP_TRANSPORT, WebserverConfiguration.JSON_FACTORY, configuration.getCredential()).setApplicationName("The name of my app on Google Play").build();
    AndroidPublisher.Purchases purchases = publisher.purchases();

    SubscriptionPurchase purchase = purchases.subscriptions().get("the.packagename.of.my.app", "subscriptionId", "billing token sent by the app").execute();

    //do something or return
    return purchase;
}
Static answered 13/1, 2018 at 10:52 Comment(6)
Should we encrypt the contents of the json file while storing it in a location, otherwise I see that it is easily read by anyone who can access it. I am thinking the content should be stored in an encrypted file and when we read, we should decrypt it and then supply it.Planck
@Planck Not necessarily. The server will start with a special user which is the only one that has the read permission to this file. (Permission rwx --- ---). Only this user and root could read this file and if anybody gains root access, then all is lost anyway.Static
AndroidPublisher publisher = new AndroidPublisher.Builder(WebserverConfiguration.HTTP_TRANSPORT, WebserverConfiguration.JSON_FACTORY, WebserverConfiguration.getCredential()).setApplicationName(String.valueOf(R.string.app_name)).build(); Getting null point exception in this line @StaticAgonized
@MuntasirAonik You don't even say what is null, so there is too little information for me to answer this.Static
Finally, after a lot of googling, your solution finally worked for me :), I had to make some changes in your code (maybe to port into Kotlin). Google should document service account authentication properly.Tiemannite
Awesome, now mine works too. One thing, once you give the permission to your user it may not be immediately propagated to Google services. I updated a Google subscription description for the app and it worked immediately.Denbighshire
M
1

There are complete code samples and documentation for doing this in Java here

In the Java source code this authorizes like this

private static Credential authorizeWithServiceAccount(String serviceAccountEmail)
            throws GeneralSecurityException, IOException {
        log.info(String.format("Authorizing using Service Account: %s", serviceAccountEmail));

        // Build service account credential.
        GoogleCredential credential = new GoogleCredential.Builder()
                .setTransport(HTTP_TRANSPORT)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountId(serviceAccountEmail)
                .setServiceAccountScopes(
                        Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER))
                .setServiceAccountPrivateKeyFromP12File(new File(SRC_RESOURCES_KEY_P12))
                .build();
        return credential;
    }
Muhammad answered 10/1, 2018 at 10:6 Comment(3)
I cant instantiate the GoogleAuthorizationCodeFlow with the 'clientId', but in the next step of https://developers.google.com/api-client-library/java/google-api-java-client/oauth2 they say 'This flow is implemented using GoogleAuthorizationCodeFlow. The steps are: End-user logs in to your application. You will need to associate that user with a user ID that is unique for your application. - this "user ID" confuses me. First of all I thought this is the GoogleID (you get when a user logs in with Google Sign-in) but maybe I was wrong and this is some random code I can assign. Will try that.Static
Added a link to the official samples, hopefully they explain better than my previous answer.Muhammad
This seems helpful. I will try to find a solution with this examples.Static

© 2022 - 2024 — McMap. All rights reserved.