Google API Offline Access Using OAuth 2.0 Refresh Token

See also: Google Analytics API Offline Access with Service Account and ColdFusion

Offline access for Google APIs is achieved through a refresh token that is issued when a user first visits the app and grants offline access. Essentially, you have to ask for offline access, Google warns them that you are asking for it, and then they have to grant it. The return contains the golden ticket refresh token.

The first time a given user’s browser is sent to this URL, they see a consent page. If they grant access, then the response includes an authorization code which may be redeemed for an access token and a refresh token.

Once a refresh token is obtained, it can be used to access Google APIs that the user has access to without re-authorizing.

See Google’s documentation on OAuth 2 for web apps and, for information regarding the refresh token, the Offline Access section for more information.

Asking for Offline Access

Parameters for Login URL

Client id and client secret are set by Google when the app is registered for api access in the Google APIs Console.

The redirect uri is a location on the server that the user is sent to after authenticating. This uri is registered in the Google APIs Console during app registration.

These values can be included as a separate file (vars.inc in the demo code) so the values can easily be swapped out on a per app basis.

$client_id = "1111111111111.apps.googleusercontent.com"; //your client id
$client_secret = "xXasdfd212312_dfdf dfxxdf"; //your client secret 
$redirect_uri = "http://YOUR-SITE.com/YOUR-PATH/";
$scope = "https://GOOGLE-SCOPE-TO-ACCESS"; //google scope to access
$state = "profile"; //optional - could be whatever value you want
$access_type = "offline"; //optional - allows for retrieval of refresh_token for offline access

User Login URL

The login URL will prompt the user for permission to access their Google content via the app and a “code” request variable will be returned in the URL. See Forming the URL for more detailed information.

$loginUrl = sprintf("https://accounts.google.com/o/oauth2/auth?scope=%s&state=%s&redirect_uri=%s&response_type=code&client_id=%s&access_type=%s", $scope, $state, $redirect_uri, $client_id, $access_type);

<a href="<?php echo $loginUrl ?>">Login with Google account using OAuth 2.0</a>

Returned URL example (http://YOUR-SITE.com/YOUR-PATH/ is your redirect uri):


http://YOUR-SITE.com/YOUR-PATH/?state=profile&code=1/fFBGRNJru1FQd44AzqT3Zg

Get Access Token and Refresh Token

They said “Yes!” Initial consent received for offline access and a “code” was returned as a request parameter:

//Initial grant for access approved by user returns 'code' URL param
if(isset($_REQUEST['code'])){
    $accessToken = get_oauth2_token($_REQUEST['code'],"online");
}

The get_oauth2_token does double duty. It will return an access token for either online or offline access grants.

For the initial grant, if a refresh token is returned, the function puts the refresh token into ‘global $refreshToken’

Why not store the refresh token here instead of putting it in a var? Because there is no reference for the refresh token at this point other than the scope of the grant request. You don’t know who it belongs to. The initial access token returned with the refresh token can be used to query the API and get data to associate with the refresh token for the database.

If there is a known value (like it belongs to you) to associate with the refresh token that can be used to retrieve it later on, put a call to the database save function in here.

//returns session token for calls to API using oauth 2.0
//set global refreshToken var if refresh token is returned
function get_oauth2_token($grantCode,$grantType) {
	global $client_id;
	global $client_secret;
	global $redirect_uri;
	
	$oauth2token_url = "https://accounts.google.com/o/oauth2/token";
	$clienttoken_post = array(
	"client_id" => $client_id,
	"client_secret" => $client_secret);

	if ($grantType === "online"){
		$clienttoken_post["code"] = $grantCode;	
		$clienttoken_post["redirect_uri"] = $redirect_uri;
		$clienttoken_post["grant_type"] = "authorization_code";
	}
	
	if ($grantType === "offline"){
		$clienttoken_post["refresh_token"] = $grantCode;
		$clienttoken_post["grant_type"] = "refresh_token";
	}
	
	$curl = curl_init($oauth2token_url);

	curl_setopt($curl, CURLOPT_POST, true);
	curl_setopt($curl, CURLOPT_POSTFIELDS, $clienttoken_post);
	curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

	$json_response = curl_exec($curl);
	curl_close($curl);

	$authObj = json_decode($json_response);
	
	//if offline access requested and granted, get refresh token
	if (isset($authObj->refresh_token)){
		global $refreshToken;
		$refreshToken = $authObj->refresh_token;
	}

	$accessToken = $authObj->access_token;
	return $accessToken;
}

Storing the Refresh Token

The refresh token will need to be stored for retrieval in the future. This example puts it in a MySQL database.

If a refresh token has been granted, store it in a database. Otherwise, retrieve a refresh token from the database. And, use the refresh token to set an access token for API calls.

//get name on account to use as reference in database for retrieving the refresh token 
//can be done with data from primary API that is being queried

$googleUserInfoAPI = "https://www.googleapis.com/oauth2/v1/userinfo";

if (isset($accessToken)){
    //got access token, get data
    $accountObj = call_api($accessToken, $googleUserInfoAPI);
    $account_name =  $accountObj->name;
}
	
//refresh token handling - save to db if returned with access token
//or retrieve from db if needed for app
if(isset($refreshToken)){
    $accessToken = dbRefreshToken($account_name,$demo_scope,$refreshToken);
} else {
    $accessTokenFromRefresh = dbRefreshToken('ACCT_NAME_HERE',$demo_scope);
}

‘ACCT_NAME_HERE’ is a string that is used to identify for retrieval the refresh token you want to use.

The dbRefreshToken function for saving or retrieving from database and returning an access token:

function dbRefreshToken($name,$scope,$refreshToken = ""){
    global $serverpath;
    $path = $serverpath."/config/config.php";
    include_once($path);
    $path = $serverpath."/config/db.php";
    include_once($path);

    if ($conn){
	if (strlen($refreshToken)){
	//if refreshToken in param list, save to db
	    $query = "INSERT INTO tokens (name, scope, token) VALUES (:name, :scope, :refreshToken)";
	    $result = $conn->prepare($query); 
	    $result->bindValue(':name', $name, PDO::PARAM_STR);
	    $result->bindValue(':scope', $scope, PDO::PARAM_STR);
	    $result->bindValue(':refreshToken', $refreshToken, PDO::PARAM_STR);
	    $result->execute(); 
	    $token = $refreshToken;
       } else {
        //else retrieve refresh token from db
	    $query = "SELECT token from tokens where name = :name and scope = :scope";
	    $result = $conn->prepare($query);
	    $result->bindValue(':name',$name, PDO::PARAM_STR);
	    $result->bindValue(':scope', $scope, PDO::PARAM_STR);
	    $result->execute();
	    $row = $result->fetch(PDO::FETCH_ASSOC);
	    $token = $row["token"];
        }

    mysql_close($conn);
		
    $accessTokenfromRefresh = get_oauth2_token($token,"offline");
    return $accessTokenfromRefresh;
   }
}

Note that config.php contains your database configuration parameter (username, password, database name) and db.php contains the actual connection string. Put them in a folder and restrict web browsing to that folder.

The table is called ‘tokens’ and the columns are name, scope, and token.

Demo

Download code

Posted in OAuth, Web development. Tags: , , . Permalink. Both comments and trackbacks are closed.

5 Comments

  1. February 20, 2012 at 9:58 pm | Permalink

    @Brad

    Right, once you have the refresh token you will not need to log in again. Just retrieve it from the db and use it to get an access token.

    The reason it fails on refresh is because it is using the URL param of code again and failing the auth since the code can only be used once.

    Add the access token to session and it will work until the access token expires. There is a time limit returned with the access token and I believe it’s good for several hours.

  2. Brad
    February 20, 2012 at 9:34 pm | Permalink

    Heads up! I threw you some coins.

    You might want to check this page out (http://www.jensbits.com/thank-you/) It’s where PayPal redirected me when it was all done.

    Anyway, thanks for the quick response. That’s exactly how I have it configured. I wonder why the error hmmm.

    Well, I have it returning my profile image now, but if I do a refresh on the page, it goes away.

    Any ideas?

    The good part about the offline access / refresh token is so that once I give it permission and it stores the tokens, I could view that page without being logged in or having any cookies and it still pull the data, right?

  3. February 20, 2012 at 9:12 pm | Permalink

    @Brad

    You can create your connection as such:

    $conn = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);

    And close it like this:

    mysql_close($conn);

    Substitute your variables for the connection of course. The mysql_close function needs the existing connection as an argument.
    Maybe you already closed it?? Or, didn’t supply the right argument variable name??

  4. Brad
    February 20, 2012 at 8:50 pm | Permalink

    Well, I just learned about PDO.

    I got this script connected to the database.
    I even got my token back and I can see that it saved to the database, woohoo….

    But I’m still trying to workout why my script still returns this….

    Warning: mysql_close(): supplied argument is not a valid MySQL-Link resource in……..

    and the ol’ “I got nothing” at the end. haha

  5. Brad
    February 20, 2012 at 8:20 pm | Permalink

    I’ve been searching for an article like this for a while.
    I have everything in place, but connecting it to the database to store the tokens.

    I’m not familiar with the way you are building and executing the queries. Could you pretty pretty please give me an example of how the files look that you mention here…

    “Note that config.php contains your database configuration parameter (username, password, database name) and db.php contains the actual connection string”