google-api-php-client: Sporadic "Invalid Credentials" Issues - Service Account

I seem to be having weird issues with “Invalid Credentials” errors, service accounts and the Search Console API. Sometimes everything works and I can access the API, then some time later I may try an access the SC API and I get an “Invalid Credentials” error. This is without changing any code.

Edit: Very weird. I swapped around my scopes and then it started working again. Nothing else was changed. Why would swapping my scopes around every so often work?

Here is what I am using:

class Google {

    private function googleApiAuthorise()
    {

        $client = new \Google_Client();
        putenv('GOOGLE_APPLICATION_CREDENTIALS=' . base_path() . '/keys/Tool-xxxxxxxxx.json');
        $client->useApplicationDefaultCredentials();
        $client->setSubject(example@example.iam.gserviceaccount.com');
        $client->setApplicationName("Tool");
        $scopes = ['https://www.googleapis.com/auth/webmasters.readonly', 'https://www.googleapis.com/auth/webmasters'];
        $client->setScopes($scopes);

        if( $client->isAccessTokenExpired()) {

            $client->refreshTokenWithAssertion();

        }

        return $client;

    }

    public function getSearchAnalytics($search_type = 'web')
    {

        $authorise = Google::googleApiAuthorise();

        $service = new \Google_Service_Webmasters($authorise);

        $request = new \Google_Service_Webmasters_SearchAnalyticsQueryRequest;

        $request->setStartRow(0);
        $request->setStartDate('2016-06-01');
        $request->setEndDate(Carbon::now()->toDateString());
        $request->setSearchType($search_type);
        $request->setRowLimit(200);
        $request->setDimensions(array('date','page','country','device','query'));

        $query_search = $service->searchanalytics->query("http://www.example.com/", $request); 
        $rows = $query_search->getRows();

        return $rows;

    }
}

The weird thing is it works sometimes. Is this some sort of bug? I’ve followed pretty much every tutorial out there about service accounts and this API client, but it still seems to be quite random as to why sometimes I can use the API and other times I get an “Invalid Credentials” error.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 22 (9 by maintainers)

Commits related to this issue

Most upvoted comments

Okay, I just figured out what’s going on. Two weeks before your PR, a new version of Stash was finally tagged with the fix I added 8 months ago. So this is now fixed with v0.14.2.

Since the PSR-6 spec says the calling library SHOULD verify the cache hit, the changes in #150 are valid, even though the spec ALSO says get should return NULL if there is no hit (making the call to isHit unnecessary).

Either way this issue is fixed!

I ran into this problem and have been working around it for months; finally got around to tracing down this bug. The Stash library’s response appears to include expired cache data which is properly flagged as a Miss (which could be a benefit for some situations), however the Google Auth library never tests it to confirm it should be used. This pull request does a proper verification of the response; and allows us to replace hacks like the one suggested in October by @chonthu (which was clearing the entire Stash cache on every hit). My client setup code now no longer needs to test for expiration or manually refresh the tokens.

@omnicodagithub I had the same issue, but I think I have a solution, @bshaffer does some checks for cache in the library prebuilt, can you try this?

  $cache = new Stash\Pool(new Stash\Driver\FileSystem); // or whatever
  $client->setCache($cache);

   if( $client->isAccessTokenExpired() ){
        $cache->clear();
         $client->refreshTokenWithAssertion();
   }

To fix this in the auth library, I’ve added https://github.com/google/google-auth-library-php/pull/138, which is not ideal (the call should be unnecessary) but will get the job done.

Another option would be to stop using tedivm/stash by default, which is what we should have done to start, but it would break B.C.

When you say “sporadic” do you mean after 3600 seconds? That would be the time when the access token is expired and you would have to get a new one.

I am having the same issue and i do understand that it is due to the expired access token. This is my particular code sequence (which is quite similar to yours):

    /* This env variable holds the path to the keyfile with the JSON based certificate. It will be required by the Google API Client */
    putenv( 'GOOGLE_APPLICATION_CREDENTIALS=' . $objApplication->getConfig( 'App::GoogleAuthServiceAccoutKeyAsJson' ) );
    try{

        /* Using Stash for caching */
        //$objCache = new Pool( new FileSystem );

        //$objCacheLogger = new Logger;
        //$hdTokenCallback = function( $cacheKey, $accessToken ) use ( $objCacheLogger ) {
        //    $objCacheLogger->debug( sprintf( 'A new access token \'%s\' has beeen received for the cache key %s', $accessToken, $cacheKey ) );
        //};

        /* Creating the client */
        $objGoogleApiClient = new \Google_Client();
        $objGoogleApiClient->useApplicationDefaultCredentials();
        $objGoogleApiClient->setApplicationName( 'myTestApplication' );
        $objGoogleApiClient->setAccessType( 'offline' );
        $objGoogleApiClient->setScopes( [ 'https://www.googleapis.com/auth/webmasters.readonly' ] );
        //$objGoogleApiClient->setCache( $objCache );
        //$objGoogleApiClient->setTokenCallback( $hdTokenCallback );

        /* Refresh token when expired */
        if( $objGoogleApiClient->isAccessTokenExpired() ){
            $objGoogleApiClient->refreshTokenWithAssertion();
        }

        /* Creating the actual service */
        $this->objGoogleService = new \Google_Service_Webmasters( $objGoogleApiClient );

    } catch( \Google_Exception $objException ){
        die(" An exception has occured!" );
    }

I have also diggged into what feels like the entire documentation there is in the Internet 😉. But i still get the expiration after 3600. I am as well using a service account and this code runs on the console.

What am i doing wrong, why am getting the expiration? Is this part

        /* Refresh token when expired */
        if( $objGoogleApiClient->isAccessTokenExpired() ){
            $objGoogleApiClient->refreshTokenWithAssertion();
        }

not enough to do the refreshing? I used a debugger but could not find a refresh token passed back to Client.php, it was always null. Recreated millions of service accounts and went through the initial authentication process expecting to get one, but none which i could save away and use for later refreshing process.

As opposed to omnicodagithub i am getting the 401 not sporadically but regularly after one hour.

Above code is using google/apiclient v2.0.3.

Thanks for any advice.