Google API Client "refresh token must be passed in or set as part of setAccessToken"
Asked Answered
H

11

19

I am currently facing a very strange problem, indeed I've been following this very same guide (https://developers.google.com/google-apps/calendar/quickstart/php) from Google API documentation. I tried it twice, at the first time it work like a charm but after the access token had expire the script provided straight by Google API Doc was unable to refresh it.

TL;DR

Here is the error message:

sam@ssh:~$ php www/path/to/app/public/quickstart.php


Fatal error: Uncaught exception 'LogicException' with message 'refresh token must be passed in or set as part of setAccessToken' in /home/pueblo/www/path/to/app/vendor/google/apiclient/src/Google/Client.php:258
Stack trace:
#0 /home/pueblo/www/path/to/app/public/quickstart.php(55): Google_Client->fetchAccessTokenWithRefreshToken(NULL)
#1 /home/pueblo/www/path/to/app/public/quickstart.php(76): getClient()
#2 {main}
  thrown in /home/pueblo/www/path/to/app/vendor/google/apiclient/src/Google/Client.php on line 258

Here is the part of the php script from google I've modified:

require_once __DIR__ . '/../vendor/autoload.php';

// I don't want the creds to be in my home folder, I prefer them in the app's root
define('APPLICATION_NAME', 'LRS API Calendar');
define('CREDENTIALS_PATH', __DIR__ . '/../.credentials/calendar-php-quickstart.json');
define('CLIENT_SECRET_PATH', __DIR__ . '/../client_secret.json');

I also modified the expandHomeDirectory so I could "disable" it without modifying too much code:

function expandHomeDirectory($path) {
  $homeDirectory = getenv('HOME');
  if (empty($homeDirectory)) {
    $homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH');
  }
  return $path;
  // return str_replace('~', realpath($homeDirectory), $path);
}

So to check if I was wrong or if Google was, I did an experiment: yesterday night I launch the quickstart script from ssh to check if it was working, and indeed it was, so I decided to check this morning if it still working just as it was before I slept and it wasn't so I think there's something wrong with Google's quickstart.php.

I hope someone could help me, I already checked all the other posts about the subject but they are all outdated.

Hark answered 4/9, 2016 at 8:5 Comment(3)
I think this SO question can help you.Arkansas
No, indeed it look like a valid answer however the bug this user reported had already been fixed in the code I'm using since he submit a ticket to Google who fixed it. But thanks for trying to help me :)Hark
As of 20 November, 2017, the Google sample PHP code published as quickstart.php is still exhibiting the error that you asked about...Shaving
C
29

I got the same problem recently and i solved it with this.

<?php
 $client->setRedirectUri($this->_redirectURI);
 $client->setAccessType('offline');
 $client->setApprovalPrompt('force');

I explain ..... Refresh token is not returned because we didnt force the approvalPrompt. The offline mode is not enought. We must force the approvalPrompt. Also the redirectURI must be set before these two options. It worked for me.

This is my full function

<?php
     private function getClient()
     {
        $client = new Google_Client();
        $client->setApplicationName($this->projectName);
        $client->setScopes(SCOPES);
        $client->setAuthConfig($this->jsonKeyFilePath);
        $client->setRedirectUri($this->redirectUri);
        $client->setAccessType('offline');
        $client->setApprovalPrompt('force');

       // Load previously authorized credentials from a file.
       if (file_exists($this->tokenFile)) {
         $accessToken = json_decode(file_get_contents($this->tokenFile), 
         true);
      } else {
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));

        if (isset($_GET['code'])) {
            $authCode = $_GET['code'];
            // Exchange authorization code for an access token.
            $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
            header('Location: ' . filter_var($this->redirectUri, 
            FILTER_SANITIZE_URL));
            if(!file_exists(dirname($this->tokenFile))) {
                mkdir(dirname($this->tokenFile), 0700, true);
            }

            file_put_contents($this->tokenFile, json_encode($accessToken));
        }else{
            exit('No code found');
        }
    }
    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) {

        // save refresh token to some variable
        $refreshTokenSaved = $client->getRefreshToken();

        // update access token
        $client->fetchAccessTokenWithRefreshToken($refreshTokenSaved);

        // pass access token to some variable
        $accessTokenUpdated = $client->getAccessToken();

        // append refresh token
        $accessTokenUpdated['refresh_token'] = $refreshTokenSaved;

        //Set the new acces token
        $accessToken = $refreshTokenSaved;
        $client->setAccessToken($accessToken);

        // save to file
        file_put_contents($this->tokenFile, 
       json_encode($accessTokenUpdated));
    }
    return $client;
}
Cameraman answered 14/4, 2017 at 13:33 Comment(7)
Thanks for your answer ! I hope it'll help a lot of people. I've switched to Apple iCloud's CalDAV API because it was way simpler to use (no complicated token or so…) but thanks anyway ^^Hark
It's a pleasureCameraman
Don't forget that you will need to set the access token and the created time in your saved file each time the access token is renewed. The $client->setTokenCallback(...) gives you a hook to store the new token each time the client (automatically) refreshes it. If you don't, then you end up refreshing the token on every single API call, from one hour after the initial authorisation. That may be okay if just have a single scheduled API call once a day, but adds a lot of overhead if it runs much more often.Fennelly
@UlrichDohou you write "Also the redirectURI must be set after these two options" but in your example redirectURI is before these two options. ??Kenric
@Fennelly what about overwriting the old access token with the new one? Then you get the latest access token and that's it... ?Kenric
@Kenric it was a mistake. Tks a lot.Cameraman
Tho you are not using accessTokenUpdated anywhere what is the purpose of that?Ivanivana
F
6

My advice is save refresh token to .json immediately after get access token and if access token expired use refresh token.

In my projects work this way:

public static function getClient()
{
    $client = new Google_Client();
    $client->setApplicationName('JhvInformationTable');
    $client->setScopes(Google_Service_Calendar::CALENDAR_READONLY);
    $client->setAuthConfig('credentials.json');
    $client->setAccessType('offline');

    // Load previously authorized credentials from a file.
    $credentialsPath = 'token.json';
    $credentialsPath2 = 'refreshToken.json';
    if (file_exists($credentialsPath)) {
        $accessToken = json_decode(file_get_contents($credentialsPath), true);
    } else {
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        //printf("Open the following link in your browser:\n%s\n", $authUrl);
        //print 'Enter verification code: ';
        $authCode = trim(fgets(STDIN));

        //echo "<script> location.href='".$authUrl."'; </script>";
        //exit;

        $authCode ='********To get code, please uncomment the code above********';

        // Exchange authorization code for an access token.
        $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
        $refreshToken = $client->getRefreshToken();

        // Check to see if there was an error.
        if (array_key_exists('error', $accessToken)) {
            throw new Exception(join(', ', $accessToken));
        }

        // Store the credentials to disk.
        if (!file_exists(dirname($credentialsPath))) {
            mkdir(dirname($credentialsPath), 0700, true);
        }
        file_put_contents($credentialsPath, json_encode($accessToken));
        file_put_contents($credentialsPath2, json_encode($refreshToken));
        printf("Credentials saved to %s\n", $credentialsPath);
    }
    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) {
        $refreshToken = json_decode(file_get_contents($credentialsPath2), true);
        $client->fetchAccessTokenWithRefreshToken($refreshToken);
        file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
    }
    return $client;
}
Flowerlike answered 31/8, 2018 at 9:30 Comment(0)
C
4

I have faced the same issue with new google api library. Search for solution brought the following link: RefreshToken Not getting send back after I get new token google sheets API

Based on that information, I have modified the quickstart code part to fit my needs. After first authorization with Google I have obtained drive-php-quickstart.json which contains refresh_token that expires in 3600 seconds or one hour. The refresh token is issued only once, so if it is lost, then re-authorization is required. So, that to have it always in drive-php-quickstart.json I have done following:

// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
// save refresh token to some variable
$refreshTokenSaved = $client->getRefreshToken(); 

// update access token
$client->fetchAccessTokenWithRefreshToken($refreshTokenSaved); 

// pass access token to some variable
$accessTokenUpdated = $client->getAccessToken();

// append refresh token
$accessTokenUpdated['refresh_token'] = $refreshTokenSaved;

// save to file
file_put_contents($credentialsPath, json_encode($accessTokenUpdated)); 
}
Colquitt answered 14/9, 2016 at 11:15 Comment(3)
Thank you for answering ! I'll see if it's a viable solution since it'll be greatly helpful. But I'm very busy now so don't expect answer too fast.Hark
@SamuelPrevost I am using Sheet API v4.. having same code but it is still giving same error "refresh token must be passed in or set as part of setAccessToken”Eucalyptol
@HamzaZafeer have you tried all the responses in this thread ? I don't know how to help you man, it's been a year. I don't even know what "Sheet API v4" is. Just do a backup of your current setup and then try every solutions here and stop till you find a working one. Good luck.Hark
G
3

just some update for anyone having trouble with this message, mostly it's because only first command fetchAccessTokenWithAuthCode() generates credencials that contains the refresh token (technically forever valid - no 2 hours validity if you dont revoke it). When you get the new one it replaces the original one yet it does not contains the refresh token that it needs so next time you need to update the token, it will crash. This can be easily fixed by replacing the refresh function to for example this:

  // Refresh the token if it's expired.
  if ($client->isAccessTokenExpired()) {
    $oldAccessToken=$client->getAccessToken();
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    $accessToken=$client->getAccessToken();
    $accessToken['refresh_token']=$oldAccessToken['refresh_token'];
    file_put_contents($credentialsPath, json_encode($accessToken));
}

Now everytime you update the access token your refresh token is passed down too.

Gamo answered 19/5, 2017 at 6:44 Comment(0)
C
1

I had the same issues and finally go this to work:

Backstory:

I received the same error. Here is what I found:

This error:

PHP Fatal error: Uncaught LogicException: refresh token must be passed in or set as part of setAccessToken in /Library/WebServer/Documents/Sites/test/scripts/vendor/google/apiclient/src/Google/Client.php:267

Is referencing the update access token (aka Refresh) method:

$client->fetchAccessTokenWithRefreshToken($refreshTokenSaved);

Why was it failing? Long story short, I realized when I printed out the $accessToken array, which comes from decoding this json file (per the quickstart code you posted/that comes from google)

credentials/calendar-php-quickstart.json

I found the error due to how the accessToken array prints out when I print_r:

Array ( [access_token] => Array ( [access_token] => xyz123 [token_type] => Bearer [expires_in] => 3600 [refresh_token] => xsss112222 [created] => 1511379484 )

)

Solution:

$refreshToken = $accessToken["access_token"]["refresh_token"];

right before this line:

    $client->fetchAccessTokenWithRefreshToken($refreshToken);

I can finally refresh the token as needed when it expires in an hour. I think the devs of this article assumed the array prints as:

Array ( [access_token] => xyz123 [token_type] => Bearer [expires_in] => 3600 [refresh_token] => xsss112222 [created] => 1511379484 )

so they thought you could simply do $accessToken["refresh_token"]; That is not correct.

Now we have a valid value for $refreshToken, so the error should go away if you do this. I also updated the author via the feedback link to let them know about this, in case other php devs encounter this issue. Hopefully this helps somebody. My apologies if I formatted this post poorly I am new to S.E. I just wanted to share as I finally got this to work.

Cola answered 22/11, 2017 at 21:41 Comment(0)
M
1

You need to serialize the accestoken when you write it to the credentialsPath.

 // Exchange authorization code for an access token.
    $accessToken = $client->authenticate($authCode);

    // Store the credentials to disk.
    if(!file_exists(dirname($credentialsPath))) {
        mkdir(dirname($credentialsPath), 0700, true);
    }
    $serArray = serialize($accessToken);
    file_put_contents($credentialsPath, $serArray);
    printf("Credentials saved to %s\n", $credentialsPath);

And when you read from the file you need to unserialize it.

if (file_exists($credentialsPath)) {
    $unserArray =  file_get_contents($credentialsPath);
    $accessToken = unserialize($unserArray);

}

Full function

function getClient() {
    $client = new Google_Client();
    // Set to name/location of your client_secrets.json file.
    $client->setAuthConfigFile('client_secret.json');
    // Set to valid redirect URI for your project.
    $client->setRedirectUri('http://localhost');
    $client->setApprovalPrompt('force');

    $client->addScope(Google_Service_YouTube::YOUTUBE_READONLY);
    $client->setAccessType('offline');

    // Load previously authorized credentials from a file.
    $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);


    if (file_exists($credentialsPath)) {
        $unserArray =  file_get_contents($credentialsPath);
        $accessToken = unserialize($unserArray);

    } else {
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        printf("Open the following link in your browser:\n%s\n", $authUrl);
        print 'Enter verification code: ';
        $authCode = trim(fgets(STDIN));

        // Exchange authorization code for an access token.
        $accessToken = $client->authenticate($authCode);

        // Store the credentials to disk.
        if(!file_exists(dirname($credentialsPath))) {
            mkdir(dirname($credentialsPath), 0700, true);
        }
        $serArray = serialize($accessToken);
        file_put_contents($credentialsPath, $serArray);
        printf("Credentials saved to %s\n", $credentialsPath);
    }

    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) {
        $client->refreshToken($client->getRefreshToken());
        file_put_contents($credentialsPath, $client->getAccessToken());
    }
    return $client;
}
Million answered 6/3, 2018 at 12:46 Comment(0)
M
0

Google has updated their PHP Quickstart, with an improved method to handle this:

Snippet below:

// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);

// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
Matchless answered 4/12, 2016 at 6:2 Comment(2)
I using updated quickstart , but still it giving me error? why ?Eucalyptol
As of 20 November, 2017, the Google sample PHP code published as quickstart.php is still exhibiting the error that the original poster asked about.Shaving
M
0

In my case I had forgotten to set the access type as "offline" without which the refresh token was not being generated.

$client->setAccessType('offline');

Once this is done, the sample code given in the google documentation will work.

// Exchange authorization code for an access token.
// "refresh_token" is returned along with the access token
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);


// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
Matthia answered 28/3, 2017 at 8:10 Comment(0)
B
0

So After some time vieweing this code:

// Exchange authorization code for an access token.
// "refresh_token" is returned along with the access token
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);


// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}

all was needed is this change:

// Exchange authorization code for an access token.
// "refresh_token" is returned along with the access token
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);


// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($accessToken);
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}

since this function $client->getRefreshToken() returns null, and if you provide the $accessToken directly, it will work just fine and update your file, hope it resolves somebody issue.

Borsch answered 4/1, 2018 at 18:24 Comment(0)
P
0

You can set the scopes here according to your needs.

    $google_client->setClientSecret($clientSecret);
    $google_client->setRedirectUri(home_url());
    $google_client->setScopes([
        'https://www.googleapis.com/auth/analytics',
        'https://www.googleapis.com/auth/webmasters.readonly',
        'https://www.googleapis.com/auth/webmasters',
        'https://www.googleapis.com/auth/analytics.readonly',
        'https://www.googleapis.com/auth/userinfo.email',
        'https://www.googleapis.com/auth/userinfo.profile',
        'https://www.googleapis.com/auth/analytics.manage.users',
        'https://www.googleapis.com/auth/cloud-platform',
        'https://www.googleapis.com/auth/analytics.manage.users.readonly',
        'https://www.googleapis.com/auth/analytics.edit',
        'openid',
        'https://www.googleapis.com/auth/accounts.reauth'
    ]);
    $google_client->setAccessType('offline');
    $google_client->setPrompt('consent');

Way to get the Token

       if(isset($_GET['code'])){
            $google_client->fetchAccessTokenWithAuthCode($_GET['code']);
            $token = $google_client->getAccessToken();
            dd($token);
        }
Penelope answered 20/5, 2024 at 6:10 Comment(0)
C
0

You have to do 2 things and some older tutorials are wrong or at least didn't work for me recently.

First you have to set

$client->setAccessType('offline');

Second, you have to set

$client->setPrompt('consent');

Older tutorials will tell you to set setApprovalPrompt instead, but that doesn't work anymore. (It doesn't force the consent screen if the user revokes and regrants the permission and therefore you won't get the refresh token on the second authorization, only the first.)

Cluck answered 24/5, 2024 at 15:53 Comment(1)
The consent prompt was just mentioned in another answer last week. The access type has been mentioned in answers going back many years.Claudiaclaudian

© 2022 - 2025 — McMap. All rights reserved.