Google Analytics 4 API - service account has full admin privileges - still PERMISSION DENIED to all API calls

179 Views Asked by At

I have the following Java code to try and get a single metric from GA4 using the GA4 Java bindings / classes:

String propertyId = "123123123";

GoogleCredentials gc = ServiceAccountCredentials.fromStream(this.getClass().getResourceAsStream("/client_secrets.json"))
                    .createScoped(Collections.singletonList(StorageScopes.DEVSTORAGE_FULL_CONTROL));

            BetaAnalyticsDataSettings betaAnalyticsDataSettings = BetaAnalyticsDataSettings.newBuilder().setCredentialsProvider(FixedCredentialsProvider.create(gc)).build();

            BetaAnalyticsDataClient betaAnalyticsDataClient = BetaAnalyticsDataClient.create(betaAnalyticsDataSettings);

RunReportRequest request = RunReportRequest.newBuilder().setProperty("properties/" + propertyId).
                    addMetrics(Metric.newBuilder().setName("active7DayUsers")).
                    addDateRanges(DateRange.newBuilder().
                            setStartDate("2023-07-31").setEndDate("today")).build();

            RunReportResponse response = betaAnalyticsDataClient.runReport(request);

This results in an exception in the RunReportResponse generation, e. g.

com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException: PERMISSION_DENIED: Request had insufficient authentication scopes

How can I get sufficient authentication scope?

I have so far

  • Confirmed, reconfirmed and confirmed again my PropertID passed in code is for the correct GA4 property.
  • Confirmed, reconfirmed and confirmed again that the GA4 account in which this PropertyID resides is in fact the account implied by my client_secrets.json downloaded from the GA4 web interface, via the Serivce Account implied in the client_secrets.json file, via the client_email field.
  • Confirmed, reconfirmed and confirmed again that the GA4 Service Account implied by my client_secrets.json file is, in fact, the GA4 service account associated with this Account and Property I am referencing, in the GA4 web interface.
  • Confirmed, reconfirmed and confirmed again that the Account referenced and Property referenced in code above have full administrator privileges active in the GA4 web interface.

Any ideas or pointers?

I've worked through every GUI option I can find, I have full admin permissions set for the GA4 Account and Property, and using the GA4 GUI I can SEE the properties are there, data is accumulating, etc. - everything is working EXCEPT the API.

The issue is incredibly common and I've worked through several posts about this, but there is simply no resolution.

I've also tried downgrading each permission for both the Account and Property, and upgrading them in all possible combinations, keep getting

com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException: PERMISSION_DENIED: Request had insufficient authentication scope

What am I doing wrong? Is there something wrong with the code above?

Thanks

2

There are 2 best solutions below

0
Stefan On BEST ANSWER

This is now solved thanks to Shila Mosammami.

Turns out, if I use this way to get a credential to access my GA4 analytics property via the service account JSON generated file per the official procedure outlined in Google's documentation for the GA4 Java API:

GoogleCredentials gc = ServiceAccountCredentials.fromStream(this.getClass().getResourceAsStream("/client_secrets.json")).createScoped(Collections.singletonList(StorageScopes.DEVSTORAGE_FULL_CONTROL))

I -always- get

com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException: PERMISSION_DENIED: Request had insufficient authentication scopes

HOWEVER

As kindly suggested by Shila Mossasami, if I use this to get the required GA4 credentials off the service account JSON file per the official procedure outlined in Google's documentation for the GA4 Java API:

GoogleCredentials gc = GoogleCredentials.fromStream(this.getClass().getResourceAsStream("/client_secrets.json"));

it all just works and the above error

com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException: PERMISSION_DENIED: Request had insufficient authentication scopes

is no longer present.

And I can now pull statistics via the GA4 API - at last!

Here's the complete working code integrating Shila's suggestion:

 String propertyId = "123456789";            
            
            GoogleCredentials gc = GoogleCredentials.fromStream(this.getClass().getResourceAsStream("/client_secrets.json"));
            
            BetaAnalyticsDataSettings betaAnalyticsDataSettings = BetaAnalyticsDataSettings.newBuilder().setCredentialsProvider(FixedCredentialsProvider.create(gc)).build();

            BetaAnalyticsDataClient betaAnalyticsDataClient = BetaAnalyticsDataClient.create(betaAnalyticsDataSettings);                                  

            RunReportRequest request = RunReportRequest.newBuilder().setProperty("properties/" + propertyId).
                    addMetrics(Metric.newBuilder().setName("active7DayUsers")).
                    addDateRanges(DateRange.newBuilder().
                            setStartDate("2023-09-11").setEndDate("today")).build();

            RunReportResponse response = betaAnalyticsDataClient.runReport(request);

"response" then contains my response data as required.

Note that I use NetBeans 11 for this, so "client_secrets.json" is located in

Projects->GA4 Project->Other Sources->src/main/resources-><default package>/client_secrets.json

in the NetBeans 11 Project Tree Gui at the left of the NetBeans 11 interface.

If "client_secrets.json" is placed there in NetBeans 11, the file can be refereced via

this.getClass().getResourceAsStream("/client_secrets.json")

in the Java code (this took me several hours to figure out, hopefully this saves someone some effort.)

Thanks again Shila! You're the hero of the day.

8
Shila Mosammami On

Please check another option as well: Go to your analytics.google account and click on Management of access to the property or Property Access Management (Sorry my page is in Italian language and I am using Translate to English option of my browser to be comprehensible) enter image description here

then you should see the email available in client_secrets.json file here having the Administrator role.

enter image description here

Java Part:

Supposing credentialsJsonPath variable is a String and the path to your secret json file, and propertyId is the variable containing your client propertyId.
... Files.createDirectories(Paths.get(credentialsJsonPath).getParent());

                GoogleCredentials credentials =
                        GoogleCredentials.fromStream(new FileInputStream(credentialsJsonPath));
    
                BetaAnalyticsDataSettings betaAnalyticsDataSettings =
                        BetaAnalyticsDataSettings.newBuilder()
                        .setCredentialsProvider(FixedCredentialsProvider.create(credentials))
                        .build();    
    
                try (BetaAnalyticsDataClient analyticsData =
                        BetaAnalyticsDataClient.create(betaAnalyticsDataSettings)) {
                     
                    int offset=0;
                    int limit=100000;
RunReportRequest request =
                RunReportRequest.newBuilder()
                .setProperty("properties/" + propertyId)
.addDateRanges(DateRange.newBuilder().setStartDate("your start date").setEndDate("your end date"))


                .addDimensions(Dimension.newBuilder().setName("date")) 
                .addDimensions(Dimension.newBuilder().setName("eventName"))

                .addMetrics(Metric.newBuilder().setName("eventCount"))
                .addMetrics(Metric.newBuilder().setName("eventsPerSession"))
                .addMetrics(Metric.newBuilder().setName("eventValue"))
.setOffset(offset)
                .setLimit(limit)
                .build();

    RunReportResponse response = analyticsData.runReport(request);
                System.out.println(response);
 }catch(Exception e){
e.printStackTrace();
}

ofcourse, you can read any other compatible dimensions/metrics, I just used some examples.