Weblog of Alif

Exploring the Web: An Apple a day

Using OAuth 2.0 with Google API in Phonegap / ChildBrowser

September 9, 2012 -- alif

This article explains how to implement OAuth 2 with Google API (gapi) on Phonegap using Childbrowser Plugin. We will authorize Google Tasks API from Phonegap and then retrieve a list of Tasks (of default list) from Google Server. Because the App is built using Phonegap, it can be deployed in iOS, Android, Blackberry, Windows Phone with no changes!

We have created 2 javascript files (liquid.js, liquid.helper.oauth.js) which facilitates the authorization process and I shall be using those 2 files in this example. However, once the process is understood, you can easily do it on your own.


1. The Output (on Android)

The output shall look like below. Upon initial launch, the user shall be required to authorize it. After the user authorizes, on next visit, the App shall automatically redirect to the Task List Page.

Working Demo: Download and install the app from Phonegap build and try on your devices!


2. Prerequisites: Before we begin

Basically, you shall need the following configured before proceeding:

  1. Phonegap/Cordova configured with ChildBrowser plugin
  2. jquery 1.7.2 or above
  3. jquery Mobile 1.1.1
  4. Google API Client

Here's a screenshot of directory structure:

You won't have the files underlined red. So, copy the following files from github:
liquid.js
helper/liquid.helper.oauth.js
model/liquid.model.tasks.js
As for, qtaskdemo.js, we will create it. So, create an empty file qtaskdemo.main.js (or anything) for storing js code for the index.html.
Now, register a OAuth access from your Google API Console. For the Client Application Type, choose 'Installed Application' and for Installed application type, choose 'Other'. Here's a screenshot:

OAuth 2.0 Access
Then, open up liquid.js and update the values for client_id and client_secret (line 65 onwards) received from Google API.
// The client_id obtained during application registration
client_id: "###",
				
// The client secret obtained during application registration
client_secret: "###", 	


3. Getting Started: Referencing the JS Files

I am assuming the html file to be used is index.html (from Activity). So, now add/reference the Javascript files. The head shall look like below. Also, add the qtaskdemo.main.js (or else) right before body ends

        < head >
        GTask Demo
        
                
        
        
        
        
        
                
        
        
        
        
        
        
    
    < /head >  
    < body >
     ....
     ....
    
   < /body >
  

On the section 'JS to go here', add the following lines:

$(document).bind("mobileinit", function() {            
            $.mobile.touchOverflowEnabled = true; // required
            $.mobile.defaultPageTransition = 'none'; // optional - for better performance on Android
            $.mobile.loadingMessageTextVisible = true; // optional - added to show text message
            $.mobile.buttonMarkup.hoverDelay = 20; // optional added to remove sluggishness - jqm default 200
});


4. Creating an Unauthorized Page

Now that JS has been referenced, lets create an unauthorized page (add the lines below inside your body):

   

GTask Demo

Getting Started

Authorize the App from your Google Account and you are ready to go!

Powered by Task


5. Creating a Tasklist Page

Lets create a simple Tasklist Page. Copy/Add the lines below inside your body:

	

Tasks Demo

Refresh Signout


6. Adding Authorization Process

So far, JS has been added, and 2 pages has been created. Add the following lines in qtaskdemo.main.js to prepare your app to start when device is ready.

$(document).ready(function() {
	
	/* startApp after device ready */
	document.addEventListener("deviceready", startApp, false);
});


/**
 * Start the App
 */
function startApp() {

}
Now, lets add an authorization feature. The Authorization process is completed in 3 steps.
  1. A New window is opened where user has to login to their google account and allow access to the App.
  2. After successful login, an authorization code is returned
  3. Using the authorization code, make another request to google's server to get the refresh token and access token. The refresh token is permanent, however, the access Token expires in 1 hour. Thus, the refresh token needs to be stored in a local storage, as it can be used again to get new access token.
If its unclear, please spend some time on google's oauth playground for a better understanding.

Now that OAuth process is clear, lets begin development. Initially, the App should show the Authorization page and upon clicking 'Authorize & Go', we want the authorization process to begin. Lets look at authorize function of liquid.helper.oauth.

/**
 * Attempts to authorize user using OAuth 
 * Opens up Another window where user allows access or denies it.
 *   
 * @param {function} callBack   A callback function which is invoked
 */
authorize: function(callBack) {
..
}
This function does the authorization process with google server and after success/error of the process, it invokes the callback function. liquid.helper.oauth also keeps track of the the error or success in its variable status, and also stores the authorization code (auth_code) in its local variable authCode. The status are:
/** 
 * Enum for Status values
 * 
 * @enum {number}
 *  
 * SUCCESS - Successfully data received from server
 * ERROR - Error occurred when trying to receive from server
 * NOT_DETERMINED - undetermined
 */
status: { 
	SUCESS: 1, 
	ERROR: -1, 
	NOT_DETERMINED: 0 
},
So, we add the following code in our qtaskdemo.main.js file
function startApp() 
{	
   var oAuth = liquid.helper.oauth;
	
    $("#access-code").click(function(event) {
        oAuth.authorize(authorizeWindowChange);
        event.preventDefault();
    });
}
Now, we need to define authorizeWindowChange function, so we add the following function:

function authorizeWindowChange(uriLocation) {
    //console.log("Location Changed: " + uriLocation); 
    var oAuth = liquid.helper.oauth;
   
   // check the status, oAuth process is successful!	
    if (oAuth.requestStatus == oAuth.status.SUCCESS) {
        var authCode = oAuth.authCode;
        // Got the authCode, now use it to get the refresh token ?
    }
    else if (oAuth.requestStatus == oAuth.status.ERROR) 
    {
    	console.log("Error >> oAuth Processing");
    } 
    else {
        // do nothing I guess!
    }


}
We received the authorization code (authCode), now we need to use it to get refresh token and an initial access token, and also store the refresh token. The function saveRefreshToken in liquid.helper.oauth does that for you. Here's what it looks like:
/**
 * Saves the Refresh Token in a local database or localStorage
 * This method shall be invoked from externally only once after an 
 * authorization code is received from google's server. This method 
 * calls the other method (getRefreshToken) to get the refresh Token and
 * then saves it locally on database and invokes a callback function
 * 
 * @param tokenObj A Object containing authorization code
 * @param {String} tokenObj.auth_code The authorization code from google's server
 * 
 * @param {Function} callback The function to be invoked with parameters
 */
saveRefreshToken: function(tokenObj, callback) {

..
}
So, back to the authorizeWindowChange function, we invoke the function saveRefreshToken and give the function startPageTaskList as a callback. startPageTaskList simply changes the current Page to a Task List Page as shown below.
function authorizeWindowChange(uriLocation) {
    //console.log("Location Changed: " + uriLocation); 
	var oAuth = liquid.helper.oauth;
	
	// oAuth process is successful!	
    if (oAuth.requestStatus == oAuth.status.SUCCESS) {
        var authCode = oAuth.authCode;

        // have the authCode, now save the refreshToken and start Page Task List
        oAuth.saveRefreshToken({ 
        	  	auth_code: oAuth.authCode
        	  }, startPageTaskList);
        
    } 
    else if (oAuth.requestStatus == oAuth.status.ERROR) 
    {
    	console.log("Error: oAuth Processing");
    } 
    else {
        // do nothing I guess!
    }
}

function startPageTaskList() {
    $.mobile.changePage("#page-tasklist", {
        transition : "none",
    });
}

So far we have completed the authorization process. However, what if the user authorizes the App, closes it, and then relaunches it. With our current approach, the user would have to reauthorize the app again. Fortunately liquid.helper.oauth has a method isAuthorized() which returns true if the App is already authorized, false otherwise. So, we simply add that logic in startApp():
/**
 * Start the App
 */
function startApp() {
   var oAuth = liquid.helper.oauth;
	
    $("#access-code").click(function(event) {
        liquid.helper.oauth.authorize(authorizeWindowChange);
        event.preventDefault();
    });

    
    if (oAuth.isAuthorized()) {
    	/* Start Page TaskList */
    	startPageTaskList();
    }
}


7. Setting up the Task List Page (optional)

At this point, authorization is complete. I simply decided to add another extra page which lists all the tasks of the user. We would like to check on the start of page if the user is Authorized is not. If the user is not authorized, we simply direct to the Home (Unauthorized page). Also add listeners for the 2 menu buttons (refresh and signout). Also, we want to populate the page with the Tasks as well (which is achieved by populateTaskList). So, we add the lines in gtaskdemo.main.js.

(function() {

	$('#page-tasklist').live('pageshow', function(event) {
		
		if (!liquid.helper.oauth.isAuthorized()) {
			goHome();
			return;
		}
		
		$('#btn-refresh').click(function(event) {
			populateTaskList();
			event.preventDefault();
		});
		
		$('#btn-hide-error').click(function(event) {
			
			$('#qt-listview-error').hide();			
			event.preventDefault();
		});
		
		/* populateTaskList on page init */
		populateTaskList();
	
		/**
		 * Add the listeners for each of the components
		 * Listeners are for:
		 * - Title bar refresh btn (#head-menu-refresh)
		 */
		$('#head-menu-refresh').click(function(event) {
		    populateTaskList();
		    event.preventDefault();
		});
		
		
		$('#head-menu-signout').click(function(event) {
		    liquid.helper.oauth.unAuthorize();
		    goHome();
		    event.preventDefault();
		});
	
	});
})();


8. Putting it all together

So the entire code for the qtaskdemo.main.js looks like:


$(document).ready(function() {
	
	/* startApp after device ready */
	document.addEventListener("deviceready", startApp, false);
});


/**
 * Start the App
 */
function startApp() {
	
	var oAuth = liquid.helper.oauth;
	
    $("#access-code").click(function(event) {
        liquid.helper.oauth.authorize(authorizeWindowChange);
        event.preventDefault();
    });

    
    if (oAuth.isAuthorized()) {
    	/* Start Page TaskList */
    	startPageTaskList();
    }
}


function startPageTaskList() {
    $.mobile.changePage("#page-tasklist", {
        transition : "none",
    });
}



function authorizeWindowChange(uriLocation) {
    //console.log("Location Changed: " + uriLocation); 
	var oAuth = liquid.helper.oauth;
	
	// oAuth process is successful!	
    if (oAuth.requestStatus == oAuth.status.SUCCESS) {
        var authCode = oAuth.authCode;

        // have the authCode, now save the refreshToken and start Page TaskList
        oAuth.saveRefreshToken({ 
        	  	auth_code: oAuth.authCode
        	  }, function() {
        		  startPageTaskList();
        	  });
        
    } 
    else if (oAuth.requestStatus == oAuth.status.ERROR) 
    {
    	console.log("Error >> oAuth Processing");
    } 
    else {
        // do nothing I guess!
    }
}


/**
 * Populates the list of Tasks
 */
function populateTaskList() {
	$.mobile.showPageLoadingMsg("a", "Loading Tasks...");
	
	/* hide all the request Info blocks/divs */
	$('.request-info').hide();
	
	liquid.model.tasks.getList(function(data) {
        $('#qt-listview-tasks').empty();
        
        console.log(JSON.stringify(data));
        
        /* check if there's an error from server, then display
         * error and retry
         */
        if (data.error) {
        	console.log('Unable to load Task List >> ' + data.error.message);
        	$.mobile.hidePageLoadingMsg();   
            return;        	
        }
        
        /* if there are no elements in it, then
         * display the info message, and return */
        if (!data.items) {
        	$('#qt-listview-info').show();
            $.mobile.hidePageLoadingMsg();        	
            return;
        }
        
        
        for (var i = 0; i < data.items.length; i++) {
        	
        	var item = data.items[i];
        	
        	$('#qt-listview-tasks')
        		.append('
  • ' + item.title + '
  • '); } $('#qt-listview-tasks').listview('refresh'); $.mobile.hidePageLoadingMsg(); }); } function goHome() { $.mobile.changePage("#page-unauthorized", { transition : "none", reverse: false, changeHash: false }); } (function() { $('#page-tasklist').live('pageshow', function(event) { if (!liquid.helper.oauth.isAuthorized()) { goHome(); return; } $('#btn-refresh').click(function(event) { populateTaskList(); event.preventDefault(); }); $('#btn-hide-error').click(function(event) { $('#qt-listview-error').hide(); event.preventDefault(); }); /* populateTaskList on page init */ populateTaskList(); /** * Add the listeners for each of the components * Listeners are for: * - Title bar refresh btn (#head-menu-refresh) */ $('#head-menu-refresh').click(function(event) { populateTaskList(); event.preventDefault(); }); $('#head-menu-signout').click(function(event) { liquid.helper.oauth.unAuthorize(); goHome(); event.preventDefault(); }); }); })();


    9. Final Thoughts

    The above was a rough demonstration of how to use google oauth on phonegap with childbrowser. For a production, it is better to combine/merge the Javascript file into 2 or 3 files, and also JS library such as backbone.js may be considered to avoid messy JS code

    Download from Github Download & Install App