Google Oauth2 and Code Igniter
I recently had the immense pleasure of adding Google Apps API integration into a project for work. Most of the APIs are fairly straight forward, but one API in particular caused me some several hours of confusion and pain before finally getting everything straightened up and working properly, Google’s Authentication API, particularly using oauth2 for web applications. There is quite a bit of documentation on Google covering it, some crucial parts I found lacking in clarity and brevity. The documentation on how it works shows neither how it works nor how to set it up in a general sense. So, what I would like to do in this post is step through the process of writing a simple library to use oauth2 to access Google Apps. Specifically this post will focus on getting it working with CodeIgniter (CI) since it posed one unique challenge that might not exist for other frameworks. This post will not cover accessing any of the actual Google Apps APIs (other than for authentication), maybe in a later post.
There are essentially four important steps in using oauth2: 1) registering your app with Google Apps, 2) requesting authorization, 3) handling the callback, and 4) requesting the access tokens. Let’s walk through them in order.
The first step requires no code, but does require you to register your application with Google Apps. You will need to do this first since it will assign to you some codes needed to make future requests. Visit Google and follow the instructions. You will get three important pieces of information from this process you will want save, probably in your config file: the client id, the client secret, and the redirect URI. The redirect URI is where Google will redirect the user after they have accepted or denied the request to access their Google Apps account. Google will auto-fill a value; you can change this to whatever you want. If you have a controller ready to receive the callback you’ll want to change the URI to that. Your redirect URI might look something like: http://yourapp.com/yourcontroller/callback-method.
The second step is to request authorization to access certain Google Apps APIs. This step is fairly straight forward, consisting of a formatted link to Google. The link consists of a few parts and looks like this:
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=<>client_id>&redirect_uri=<redirect_url>&scope=https://www.google.com/m8/feeds https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/&access_type=offline&approval_prompt=force&state=<state>
There are quite a few parts to this URI, all of which are explained in Google’s documentation, but I’ll go over them quickly here.
- https://accounts.google.com/o/oauth2/auth?
- This is the URI to Google.
- response_type=code
- What you are requesting, this is static and you won’t change it.
- &client_id=<client_id>
- Replacewith the value Google assigned to your application when you registered in step 1.
- &redirect_uri=<redirect_uri>
- Replacewith the value you entered when you registered in step 1.
- &scope=https://www.google.com/m8/feeds https://docs.google.com/feeds/ https://docs.googleusercontent.com/ https://spreadsheets.google.com/feeds/
- This is a space delimited list of Google Apps APIs you are requesting access to. You will have to do some digging to get these, but this is a good place to start. Visit each API you want to access and look for the oauth2 scope information. I’ve left the references to Google Docs, Google Contacts, and Google spreadsheets in the example for you to compare.
- &access_type=offline
- This is optional and if absent defaults to online. If requesting offline access you will receive an extra value with the callback. This post will use offline access.
- &approval_prompt=force
- This is optional and if absent defaults to not forcing reauthentication. I will talk about this later and why you might want it.
- &state=<state>
- This is optional and the value will be sent back with callback data. It is for you to use if you want to send a piece of data to your callback method.
Since this is just a URI, you could put it straight into a view. I didn’t like the idea of exposing all that information, so I added a method in my controller (called Api) that I used in the link. The link and method looks like this:
<a href="http://myapp.com/api/requestauthorizationtoken" target="_blank">Authorize Google Apps access</a>
public function requestAuthorizationToken() {
$this->load->helper('url');
$url = 'https://accounts.google.com/o/oauth2/auth?';
$querystring = 'response_type=code';
$querystring .= '&client_id='.$this->config->item('googleapps_client_id');
$querystring .= '&redirect_uri='.$this->config->item('googleapps_redirect_uri');
$querystring .= '&scope=https://www.google.com/m8/feeds _
https://docs.google.com/feeds/ _
https://docs.googleusercontent.com/ _
https://spreadsheets.google.com/feeds/';
$querystring .= '&access_type=offline';
$querystring .= '&approval_prompt=force';
redirect($url.$querystring);
}
You can see that when the user clicks the link it will open a new window to the controller method which builds the URI and redirects to Google’s authorization screen. The user will either approve or disapprove the request and Google will redirect the user back to the redirect URI you specified when registering your application.
Now it is time for step 3: handling the callback. When Google calls your redirect URI one or two querystring values will be added to the URI depending on the options you used when requesting authorization. Firstly let’s look at those querystring values.
http://<callback_uri>?code=<authorization_code>&state=<state>
Or
http://<callback_uri>?error=access_denied&state=<state>
If the user approved the request Google will send the first querystring. If the user declined the request Google will send the second querystring. If you included the optional state parameter in your request Google will pass that value back with both querystrings. If you did not include the optional state parameter in your request that value will be absent in the callback and you will receive just the first querystring value (either code or error).
As a CI user you will realize quickly enough a problem; CI will strip the querystring values before calling your controller method. This means you will not be able to use PHP’s global $_GET array to read the values. There may be an easier way to get at those querystring values, but this is what my controller method looks like:
function callbackGoogleAuth() {
list($key, $value) = explode('=', $_SERVER['QUERY_STRING']);
switch ($key) {
case 'code':
$data['data'] = $this->_getGoogleTokens($value);
break;
case 'error':
$data['data'] = $value;
break;
default:
$data['data'] = $value;
}
$this->load->view('User/api-google-result', $data);
}
Since I am not using the optional state parameter, my method only needs to split the querystring on the equal sign. If you are using the state parameter will need to do a little more work splitting the querystring. If the authorization request was successful you will proceed to step 4 and request the access tokens. If the authorization request was declined then you will probably want to handle the error a little more gracefully than simply passing the error message back to the view as I am doing in the example method.
The final step, step 4, is where you request the access tokens, what will actually allow you to access the user’s Google Apps data. Now that you have the authorization token getting the access tokens is simply a matter of sending the request to Google. In the success condition above I am calling a private method called _getGoogleTokens and I am passing it the authorization token Google sent back. My method looks like this:
private function _getGoogleTokens($code) {
$url = 'https://accounts.google.com/o/oauth2/token';
$postData = Array();
$postData['code'] = $code;
$postData['client_id'] = $this->config->item('googleapps_client_id');
$postData['client_secret'] = $this->config->item('googleapps_client_secret');
$postData['redirect_uri'] = $this->config->item('googleapps_redirect_uri');
$postData['grant_type'] = 'authorization_code';
$retVal = $this->_curlGoogle($url, $postData);
return $retVal;
}
private function _curlGoogle($url, $data) {
$retVal = FALSE;
$curl_handler = curl_init();
$options = Array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $data
);
curl_setopt_array($curl_handler, $options);
$result = new StdClass();
$result->response = curl_exec($curl_handler);
$retVal = json_decode($result->response, TRUE);
curl_close($curl_handler);
return $retVal;
}
This part is pretty straight-forward. The method uses cURL to make the final request to Google, the response of which will be a JSON-formatted string, which is decoded into an array and passed back. Google will send back one or two tokens depending on whether your original authorization request in step 2 contained an access_type of offline or not. If you did not request offline access you will receive back only an access_token, otherwise you will receive both an access_token and a refresh_token. You will use the access_token when making API calls to the other Google Apps APIs. The refresh_token is used if you need to access a user’s Google Apps account when the user is not around. I will cover using the refresh token in a later post.
Before I end this post let me share some final notes.
If you do not set the approval_prompt option to force in step 2, Google will only send the refresh_token with the first request. Subsequent requests will not include it. If you need offline access make sure you save the refresh_token on the first request. If you do not you will have to force the user to re-authorize access. Setting the approval_prompt option to force will present the user with the approval screen each time the link is pressed and Google will send back a new access_token and a new refresh_token. During testing I did not force the approval prompt, which leads me to my second note.
If the authorization token expires and you attempt to request new access tokens, Google will send back an error stating that you have an invalid grant_type. You will become very confused. You might end up spending half a day trying to figure why your code is for some reason sending an invalid grant type. The error is misleading. Force the approval prompt on each request and things should start working. This is just one condition where that error might show up, but if you are requesting offline access and not forcing the prompt, you might see it.
If you are using offline access you may need to disable the refresh token. I have not included the code for that in the above examples, but it is included in the code below.
The examples in this post are greatly simplified and do not include much error trapping code. You will want to add some in your implementation.
In the code below I have put all the methods together in the controller object. In my implementation much of it has been split off into models and libraries, I only put it together here for convenience. I encourage you to use your discretion about how to organize your code.
I hope you have found this guide useful.
class Api {
function __construct() {
}
/********************************************************************
* Called when the user clicks the link to request Google Apps
* authorization.
********************************************************************/
public function requestAuthorizationToken() {
$this->_sendAuthorizationTokenRequest();
}
/********************************************************************
* Called by Google after the user accepts or declines authorization
* request.
********************************************************************/
public function callbackGoogleAuth() {
list($key, $value) = explode('=', $_SERVER['QUERY_STRING']);
switch ($key) {
case 'code':
$data['data'] = $this->_getGoogleTokens($value);
break;
case 'error':
$data['data'] = $value;
break;
default:
$data['data'] = $value;
}
$this->load->view('views/api-google-result', $data);
}
/********************************************************************
* Builds the authorization request link and redirects to Google
********************************************************************/
private function _sendAuthorizationTokenRequest() {
$this->CI->load->helper('url');
$url = 'https://accounts.google.com/o/oauth2/auth?';
$querystring = 'response_type=code';
$querystring .= '&client_id='.$this->CI->config->item('googleapps_client_id');
$querystring .= '&redirect_uri='.$this->CI->config->item('googleapps_redirect_uri');
$querystring .= '&scope=https://www.google.com/m8/feeds _
https://docs.google.com/feeds/ _
https://docs.googleusercontent.com/ _
https://spreadsheets.google.com/feeds/';
$querystring .= '&access_type=offline';
$querystring .= '&approval_prompt=force';
redirect($url.$querystring);
}
/********************************************************************
* Sends a revokation request to Google. Takes the refresh_token as a
* parameter. Used only when offline access is needed.
********************************************************************/
private function _sendAccessRevokationRequest($refresh) {
$url = 'https://accounts.google.com/o/oauth2/revoke?token='.$refresh;
$curl_handler = curl_init();
$options = Array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_POST => FALSE
);
curl_exec($curl_handler);
return TRUE;
}
/********************************************************************
* Sends a request to Google for access_token-s and refresh_token-s
* (when offline access is needed). Takes the authorization token as
* a parameter.
********************************************************************/
private function _getGoogleTokens($code) {
$url = 'https://accounts.google.com/o/oauth2/token';
$postData = Array();
$postData['code'] = $code;
$postData['client_id'] = $this->config->item('googleapps_client_id');
$postData['client_secret'] = $this->config->item('googleapps_client_secret');
$postData['redirect_uri'] = $this->config->item('googleapps_redirect_uri');
$postData['grant_type'] = 'authorization_code';
$retVal = $this->_curlGoogle($url, $postData);
return $retVal;
}
/********************************************************************
* Sends a request to Google to refresh a user's access_token. Only
* used when offline access is needed. Takes the refresh_token as a
* parameter.
********************************************************************/
private function _getAccessTokenFromRefresh($refresh) {
$url = 'https://accounts.google.com/o/oauth2/token';
$postData = Array();
$postData['refresh_token'] = $refresh;
$postData['client_id'] = $this->config->item('googleapps_client_id');
$postData['client_secret'] = $this->config->item('googleapps_client_secret');
$postData['grant_type'] = 'refresh_token';
$retVal = $this->_curlGoogle($url, $postData);
return $retVal;
}
/********************************************************************
* Helper function for making the cURL calls to Google.
********************************************************************/
private function _curlGoogle($url, $data) {
$retVal = FALSE;
$curl_handler = curl_init();
$options = Array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $data
);
curl_setopt_array($curl_handler, $options);
$result = new StdClass();
$result->response = curl_exec($curl_handler);
$retVal = json_decode($result->response, TRUE);
curl_close($curl_handler);
return $retVal;
}
}


