How to refresh token with Google API client?

146,063

Solution 1

So i finally figured out how to do this. The basic idea is that you have the token you get the first time you ask for authentication. This first token has a refresh token. The first original token expires after an hour. After an hour you have to use the refresh token from the first token to get a new usable token. You use $client->refreshToken($refreshToken) to retrieve a new token. I will call this "temp token." You need to store this temp token as well because after an hour it expires as well and note it does not have a refresh token associated with it. In order to get a new temp token you need to use the method you used before and use the first token's refreshtoken. I have attached code below, which is ugly, but im new at this...

//pull token from database
$tokenquery="SELECT * FROM token WHERE type='original'";
$tokenresult = mysqli_query($cxn,$tokenquery);
if($tokenresult!=0)
{
    $tokenrow=mysqli_fetch_array($tokenresult);
    extract($tokenrow);
}
$time_created = json_decode($token)->created;
$t=time();
$timediff=$t-$time_created;
echo $timediff."<br>";
$refreshToken= json_decode($token)->refresh_token;


//start google client note:
$client = new Google_Client();
$client->setApplicationName('');
$client->setScopes(array());
$client->setClientId('');
$client->setClientSecret('');
$client->setRedirectUri('');
$client->setAccessType('offline');
$client->setDeveloperKey('');

//resets token if expired
if(($timediff>3600)&&($token!=''))
{
    echo $refreshToken."</br>";
    $refreshquery="SELECT * FROM token WHERE type='refresh'";
    $refreshresult = mysqli_query($cxn,$refreshquery);
    //if a refresh token is in there...
    if($refreshresult!=0)
    {
        $refreshrow=mysqli_fetch_array($refreshresult);
        extract($refreshrow);
        $refresh_created = json_decode($token)->created;
        $refreshtimediff=$t-$refresh_created;
        echo "Refresh Time Diff: ".$refreshtimediff."</br>";
        //if refresh token is expired
        if($refreshtimediff>3600)
        {
            $client->refreshToken($refreshToken);
        $newtoken=$client->getAccessToken();
        echo $newtoken."</br>";
        $tokenupdate="UPDATE token SET token='$newtoken' WHERE type='refresh'";
        mysqli_query($cxn,$tokenupdate);
        $token=$newtoken;
        echo "refreshed again";
        }
        //if the refresh token hasn't expired, set token as the refresh token
        else
        {
        $client->setAccessToken($token);
           echo "use refreshed token but not time yet";
        }
    }
    //if a refresh token isn't in there...
    else
    {
        $client->refreshToken($refreshToken);
        $newtoken=$client->getAccessToken();
        echo $newtoken."</br>";
        $tokenupdate="INSERT INTO token (type,token) VALUES ('refresh','$newtoken')";
        mysqli_query($cxn,$tokenupdate);
        $token=$newtoken;
        echo "refreshed for first time";
    }      
}

//if token is still good.
if(($timediff<3600)&&($token!=''))
{
    $client->setAccessToken($token);
}

$service = new Google_DfareportingService($client);

Solution 2

The problem is in the refresh token:

[refresh_token] => 1\/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY

When a string with a '/' gets json encoded, It is escaped with a '\', hence you need to remove it.

The refresh token in your case should be:

1/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY

What i'm assuming you've done is that you've printed the json string which google sent back and copied and pasted the token into your code because if you json_decode it then it will correctly remove the '\' for you!

Solution 3

here is the snippet to set token, before that make sure the access type should be set to offline

if (isset($_GET['code'])) {
  $client->authenticate();
  $_SESSION['access_token'] = $client->getAccessToken();
}

To refresh token

$google_token= json_decode($_SESSION['access_token']);
$client->refreshToken($google_token->refresh_token);

this will refresh your token, you have to update it in session for that you can do

 $_SESSION['access_token']= $client->getAccessToken()

Solution 4

The access type should be set to offline. state is a variable you set for your own use, not the API's use.

Make sure you have the latest version of the client library and add:

$client->setAccessType('offline');

See Forming the URL for an explanation of the parameters.

Solution 5

The answer posted by @uri-weg worked for me but as I did not find his explanations very clear, let me reword it a little.

During the first access permission sequence, in the callback, when you get to the point where you receive an authentication code, you must save the access token and the refresh token as well.

The reason is google api sends you an access token with a refresh token only when prompting for access permission. The next access tokens will be sent without any refresh token (unless you use the approval_prompt=force option).

The refresh token you received the first time stays valid until the user revokes access permission.

In simplistic php, an example of the callback sequence would be:

// init client
// ...

$authCode = $_GET['code'];
$accessToken = $client->authenticate($authCode);
// $accessToken needs to be serialized as json
$this->saveAccessToken(json_encode($accessToken));
$this->saveRefreshToken($accessToken['refresh_token']);

And later on, in simplistic php, the connection sequence would be:

// init client
// ...

$accessToken = $this->loadAccessToken();
// setAccessToken() expects json
$client->setAccessToken($accessToken);

if ($client->isAccessTokenExpired()) {
    // reuse the same refresh token
    $client->refreshToken($this->loadRefreshToken());
    // save the new access token (which comes without any refresh token)
    $this->saveAccessToken($client->getAccessToken());
}
Share:
146,063
seorch.me
Author by

seorch.me

Updated on September 16, 2021

Comments

  • seorch.me
    seorch.me over 2 years

    I've been playing around with the Google Analytics API (V3) and have run into som errors. Firstly, everything is set up correct and worked with my testing account. But when I want to grab data from another profile ID (Same Google Accont/GA Account) I get an 403 Error. The strange thing is that data from some GA accounts will return data whilst other generate this error.

    I've revoked the token and authenticated one more time, and now it seems like I can grab data from all of my accounts. Problem solved? Not. As the access key will expire, I will run into the same issue again.

    If I have understood things right, one could use the resfreshToken to get a new authenticationTooken.

    The problem is, when I run:

    $client->refreshToken(refresh_token_key) 
    

    the following error is returned:

    Error refreshing the OAuth2 token, message: '{ "error" : "invalid_grant" }'
    

    I’ve checked the code behind the refreshToken method and tracked the request back to the “apiOAuth2.php” file. All parameters are sent correctly. The grant_type is hard coded to ‘refresh_token’ within the method, so it’s hard for me to understand what’s wrong. The parameter array looks like this:

    Array ( [client_id] => *******-uqgau8uo1l96bd09eurdub26c9ftr2io.apps.googleusercontent.com [client_secret] => ******** [refresh_token] => 1\/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY [grant_type] => refresh_token )
    

    The procedure is as follows.

    $client = new apiClient();
    $client->setClientId($config['oauth2_client_id']);
    $client->setClientSecret($config['oauth2_client_secret']);
    $client->setRedirectUri($config['oauth2_redirect_uri']);
    $client->setScopes('https://www.googleapis.com/auth/analytics.readonly');
    $client->setState('offline');
    
    $client->setAccessToken($config['token']); // The access JSON object.
    
    $client->refreshToken($config['refreshToken']); // Will return error here
    

    Is this a bug, or have I completely misunderstood something?

  • seorch.me
    seorch.me about 12 years
    Thanks jk. I've downloaded the latest version and revoked access to the app for my account. Then I granted access one more time and stored the accessToken and refreshToken. The thing is, I've always been given a refreshToken, even if the setAccessType have been omitted. Anyhow, when I run $client->refreshToken(refresh-token-key), I still get the "invalid_grant" error. I've checked the auth-url and it defaults to "force". If I change it to "auto" and runs the authenticate method, I'm not redirected as I already granted access. But the respons is an accessToken without a refresh one. Any ideas?
  • jk.
    jk. about 12 years
    @seorch.me Sounds crazy but is it possible that you have to set up a new $client ($client = new apiClient();) to use the refresh token?
  • T.B Ygg
    T.B Ygg almost 10 years
    you made my day with this :) thank you very much, a lot more simple than i thought it would be as i have been spending a lot of time getting nowhere :D
  • Mircea Sandu
    Mircea Sandu almost 10 years
    amazing mention, made my day! saved hours!
  • Gaurav Gupta
    Gaurav Gupta almost 10 years
    Instead of checking for 3600 seconds, you should use $client->isAccessTokenExpired()
  • Tropicalista
    Tropicalista over 9 years
    Please, could you explain me why this line: $redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];. Why you redirect to same page? is this necessary?
  • John Slegers
    John Slegers over 9 years
    @Tropicalista : It isn't necessary to reload the page per se, but this is the way authentication flows are typically implemented.
  • apadana
    apadana over 7 years
    but you are not using the refresh token to get a new access token if access token is expired.
  • skidadon
    skidadon over 7 years
    Small update. In the latest version, when you request a refresh token the new access token that is returned now comes with a new refresh token. So essentially, you can use the updated json token to replace the previous json token, and do not need to retain the initial access token any longer. .
  • Jason
    Jason almost 7 years
    Note that $client->isAccessTokenExpired() will still only check the times held locally to see if it thinks the token has expired. The token may still have expired and the local application will only really know when it tries to use it. In this case the API client will return an exception, and will not automatically refresh the token.
  • Jason
    Jason almost 7 years
    @seorch.me you must set $client->setApprovalPrompt('force') as well as $client->setAccessType('offline') to get a new refresh token during authorisation. Without forcing the user to approve the scope of access, Google assumes you are going to keep using the old refresh token.
  • Szymon Sadło
    Szymon Sadło over 6 years
    That answer helped me a lot, man. You probably saved me a lot of time. A lot! Thanks! I just executed sudo apt-get install ntp on my Debian machine to install NTP. It synchronized the clock and the problem was solved.
  • ninsky
    ninsky about 6 years
    "Automatically refresh" means that I just have to ask for getAccessToken() and I will get a refreshed one back? But I have to set the refresh token out of the DB first, right? Otherwise the refresh would work without a refresh token and I don't think this would work
  • Oliver Bayes-Shelton
    Oliver Bayes-Shelton almost 6 years
    perfect, worked a lot. only thing I would say is that you should explain you need to pass the json object not just the token as a string.
  • Daishi
    Daishi almost 6 years
    @OliverBayes-Shelton Hi. Thanks. I thought // setAccessToken() expects json was sufficient. Or is it for another part of the code ?
  • Truong Dang
    Truong Dang almost 5 years
    You saved my day !
  • Bjorn
    Bjorn almost 5 years
    This works great for me, but do you know if this code handles situations where a token expires due to exceeding the limit of 50 token refreshes? Details about 'Token Expiration' can be found here: developers.google.com/identity/protocols/OAuth2#expiration
  • Askerman
    Askerman almost 5 years
    I wish i could upvote this 100 times. I was about to make a hole into a wall with my keyboard after staring at "bad grant" message for several hours after trying absolutely everything to make the token work. Fricking google man, why use slashes, just why?
  • Brian C
    Brian C over 4 years
    It seems that the latest 2.0 version is now returning the refresh token in the access token array. This means that saving the access token also saves the refresh token, as the refresh token is included. In response to the refresh token expiring, I'm guessing that would have to tested for and handled explicitly - remember the 50 limit is "per user per client", ie it's 50 per client so you're unlikely to hit it, especially if you use included scopes to combine tokens.
  • Brian C
    Brian C over 4 years
    To be more specific, it looks like the Refresh token is included in your first authorization. If you save and then use it, I believe (according to others, not verified) that the refresh token continues to be returned. The doco also now says that they will auto-refresh the access token if they have a refresh token, which means it's simply a matter of managing the refresh token securely. setApprovalPrompt('force') does force a refresh token to be issued subsequently; without it you won't get another one.
  • sudip
    sudip over 3 years
    @Jason Not true now I think. I see the below return statement in the "isAccessTokenExpired" method : return ($created + ($this->token['expires_in'] - 30)) < time();