May

10

Google Analytics API Login Authentication with ColdFusion

You might also like the ColdFusion and Google Analytics: Getting Out What You Put In post.

Updated 08-02-2009: Replaced this line:

<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/#urlEncodedFormat(form.Email)#",loginAuth) />

with this:

<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/default",loginAuth) />

My Hooking into Google Analytics with ColdFusion post received a suggestion from Raymond Camden that authentication and token acquisition could be done via cfhttp for access to Google Analytics information. So, here it is again with authentication built in. I set this up with a email/password form but you could easily hard code the email and password in if you so desired.

1. Prompt for email and password of Google account that has access to Google Analytics data. This is an all-in-one example so the form submits back to its own page and processing continues.

<form action="GAwithCFlogin.cfm" method="post">
Google email <input type="text" name="Email" /><br />
Password <input type="password" name="password" /><br />
<input type="submit" />
</form>

2. Authenticate the credentials with Google.

<cfset loginAuth = googleLogin(form.Email,form.password) />

The googleLogin function sends the email/password combo along with the service and source values as URL parameters required by Google to Google’s client login URL via a post request. The source parameter is a Short string identifying your application, for logging purposes. This string should take the form: “companyName-applicationName-versionID” according to Google. You can put in your company name or whatever name you want followed by the rest of the convention Google suggests. Example: yourcompanyname-analytics-1.0

<cffunction name="googleLogin" access="public" returntype="string">
	<cfargument name="email" type="string" required="yes" default="">
	<cfargument name="password" type="string"required="yes" default="">
    <cfargument name="gaLoginUrl" type="string" required="no" default="https://www.google.com/accounts/ClientLogin">

    <cfset var loginAuth = "" />

    <cfhttp url="#arguments.gaLoginUrl#" method="post">
        <cfhttpparam name="accountType" type="url" value="GOOGLE">
        <cfhttpparam name="Email" type="url" value="#arguments.email#">
        <cfhttpparam name="Passwd" type="url" value="#arguments.password#">
        <cfhttpparam name="service" type="url" value="analytics">
        <cfhttpparam name="source" type="url" value="yourCompanyName-analytics-1.0">
    </cfhttp>

	<cfif NOT FindNoCase("Auth=",cfhttp.filecontent)>
		<cfset loginAuth = "Authorization Failed" />
	<cfelse>
		<cfset loginAuth = Mid(cfhttp.filecontent, FindNoCase("Auth=",cfhttp.filecontent) + (Len("Auth=")), Len(cfhttp.filecontent)) />
	</cfif>

    <cfreturn loginAuth />
</cffunction>

What should come back is a long string of authorization token values. There are several but we only need the Auth token. If it’s not in there, we send back “Authorization Failed.” If it’s in there, we strip it out and return it.

3. Get the accounts associated with the email. There can be more than one set up in Analytics.

<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/default",loginAuth) />

The email account is appended onto the Google URL and a get call is made from the callApi function with the loginAuth token attached as a header value.

<cffunction name="callApi" access="public" returntype="string">
    <cfargument name="gaUrl" type="string" required="yes">
    <cfargument name="authToken" type="string" required="yes">

    <cfset var authSubToken = 'GoogleLogin auth=' & arguments.authToken />
    <cfset var responseOutput = "" />

    <cfhttp url="#arguments.gaUrl#" method="get">
        <cfhttpparam name="Authorization" type="header" value="#authSubToken#">
    </cfhttp>

    <cfset responseOutput = cfhttp.filecontent />

    <cfreturn responseOutput />
</cffunction>

The header value must be in the form of Authentication: GoogleLogin auth=DF3843483…(it’s kind of long but you get the idea). This authentication is different form AuthSub Proxy authentication used in my previous post.

4. Clean up the XML that returned so we can use XmlSearch() and other handy functions available in ColdFusion. For some reason the atom specification of the XML file bungles those functions up. We need to remove the xmlns nodes from the feed element and remove the dxp prefixes. Do a cfdump on the XML before and after two lines of code used to tidy them up to seed what happens.

<cfdump var="#accountXML#">

Then roll through the accountXML with to grab all the profiles and toss them into an array so we can loop through them later.

<!---remove dxp: prefix from nodes that have it--->
	<cfset accountXML = accountXML.ReplaceAll("(</?)(\w+:)","$1") />
	<!---Strip xmlns from feed element--->
	<cfset accountXML = REReplaceNoCase(accountXML,"<feed[^>]*>","<feed>") />

	<cfset entryNodes = XmlSearch(accountXML, '//entry/') />

	<cfset profileArray = ArrayNew(1) />

	<cfloop from="1" to="#ArrayLen(entryNodes)#" index="num">
		<cfset entryStruct = StructNew() />

		<cfset entryStruct.id = entryNodes[num].id.XmlText />
		<cfset entryStruct.title = entryNodes[num].title.XmlText />
		<cfset entryStruct.tableId = entryNodes[num].tableId.XmlText />

		<cfloop from="1" to="#ArrayLen(entryNodes[num].property)#" index="i">
   			<cfswitch expression='#entryNodes[num].property[i].XmlAttributes["name"]#'>
    		    <cfcase value="ga:accountId">
       		     <cfset entryStruct.accountId = entryNodes[num].property[i].XmlAttributes["value"] />
     		   </cfcase>
      		  <cfcase value="ga:accountName">
    		        <cfset entryStruct.accountName = entryNodes[num].property[i].XmlAttributes["value"] />
 		       </cfcase>
		         <cfcase value="ga:profileId">
		               <cfset entryStruct.profileId = entryNodes[num].property[i].XmlAttributes["value"] />
	            </cfcase>
	            <cfcase value="ga:webPropertyId">
	                <cfset entryStruct.webPropertyId = entryNodes[num].property[i].XmlAttributes["value"] />
	            </cfcase>
	        </cfswitch>
	    </cfloop>

		    <cfset arrayAppend(profileArray,duplicate(entryStruct)) />

		</cfloop>

5. Loop through the profiles, grab the page views, and tally up all page views for all profiles.

<cfset totalpageviews = 0 />

		<cfloop from="1" to="#ArrayLen(profileArray)#" index="num">
	    	<cfset reqUrl = "https://www.google.com/analytics/feeds/data?ids=" & profileArray[num].tableId & "&metrics=ga:pageviews&start-date=2007-06-01&end-date=2009-05-03" />

	 		 <cfset statsXML = callApi(reqUrl,loginAuth) />
	                <!---Tidy up the XML again --->
	 		 <cfset statsXML = statsXML.ReplaceAll("(</?)(\w+:)","$1") />
			 <cfset statsXML = REReplaceNoCase(statsXML,"<feed[^>]*>","<feed>") />

			 <cfset metric = XmlSearch(statsXML, '//metric/') />

			 <cfset pageviews = metric[1].XmlAttributes["value"] />

			 <cfset totalpageviews = totalpageviews + pageviews />

			 <p>Profile: #profileArray[num].title# #NumberFormat(pageviews, ",")#</p>

		 </cfloop>

		<p>Total pageviews: #NumberFormat(totalpageviews, ",")#</p>

All done. Thank you Raymond Camden and Alex Curelea. Alex got me pointed in the right direction with his post Using the Google Analytics API – getting total number of page views. And, as I mentioned at the get go, Raymond Camden made the suggestion of using the login as some of his examples have done in the past.

Recommended Reading

Demo

Download

Information on Google’s Authentication for Installed Application ClientLogin and the Google Analytics Data API ClientLogin Username/Password Authentication.

Helpful ColdFusion books include the all-in-one ColdFusion MX 7 WACK with tons of good information even if you’ve moved up to CF8. Crazy as it seems, Adobe decided to break up ColdFusion 8 books into three volumes: Volume 1: Getting Started, Volume 2: Application Development, and Volume 3: Advanced Application Development. You may be able to pick up a used copy in good condition. Follow the book link to Amazon and look for used copies underneath the books title.

16 Comments for Google Analytics API Login Authentication with ColdFusion


Phillip Senn
May 12, 2009

Thanks Jensbits for this code!
Your name is not on the “About” page.
I would like to automatically log people into GMail once they are logged into my system.
The API documentation says “For specific service names, refer to the service documentation.”

Do you think this code can be used for authenticating someone into GMail automatically?


jen
May 12, 2009

@Phillip
As you are aware, for authenticating into Gmail you would need the service name. I can’t find a specific service name for Gmail and that may be on purpose. Google may not want or be ready for folks to hit Gmail in that manner. Not all of us are benevolent in nature.

The service name for analytics is “analytics” as seen in the code above. Other service names are listed here: http://code.google.com/apis/gdata/faq.html#clientlogin

Also, to my knowledge, there is no Gmail API from Google themselves.


Phillip Senn
May 12, 2009

Thanks Jen!
I actually found a site that showed that the following works:
https://www.google.com/accounts/ServiceLoginAuth?continue=http://mail.google.com/gmail&service=mail&Email=xxxxxxxxxx&Passwd=xxxxxxxx

It works with the form scope as well:

url:

Service:

Email address:

Password:

But right now I’m stuck because the school that I’m trying to get this to work for has some sort of GMail subdomain.

BTW, the site was:
http://www.marcofolio.net/tips/automatic_sign_into_gmail_using_a_bookmark.html


jen
May 12, 2009

@Phillip
Right you are! I was able to get an Auth code using the same code as above but changing the service to “mail”.

Now, if I could just figure out what data I can grab and how…that’s for another time but I have something in mind. UPDATE – right now it doesn’t appear that you can grab the gmail atom feed with the clientLogin. You need to use authsub login. AuthSub is the whole clunky redirect.

Of course we have to be careful about usernames and passwords in URLs like the bookmark example.


jen
May 14, 2009

@Phillip
I’ll look into that. Thanks for the link. For what I wanted to do with gmail, I found a solution using POP access that is fairly straightforward and clean.


Chris Peters
June 21, 2009

This is beautiful. I’m looking for ways to hook CFWheels.org into Google for authentication, and it looks like your googleLogin() function will do the trick.

Thanks for sharing!


Ron T
August 2, 2009

When I tried to use the code above I got the error “Content Is Not Allowed In Prolog”. I added a snippet of code to remove the “Byte-Order-Mark”. That seemed to resolve that error. Now I am getting the error “An error occured while Parsing an XML document. Premature end of file” Any thoughts?


jen
August 2, 2009

@Ron T
Google changed the feed URL. See the update to the post. Thanks for bringing this to my attention.

That line noted above should be the only one you have to change. No snippets needed.


Jon B
September 30, 2009

Thanks for this code. I do have a question. I’m working on a way to list the top 10 pages. I’ve made the changes to grab the right information from the API, but I need to know what I need to change in the cfloop to make it grab the rest of the results and not just the first one. Can someone help?

<cfset statsXML = statsXML.ReplaceAll("(
<cfset statsXML = REReplaceNoCase(statsXML,"]*>”,”") />


Jon B
September 30, 2009

Doesn’t look like the code came through. Here’s a link to the text file http://dl.getdropbox.com/u/1651587/gaapicode.txt


jen
October 1, 2009

@Jon
Loop through the entry nodes then loop the metrics and grab each dimension then each metric. Stick each dimension/metric combos in a structure and append that to an array. You can then loop through the resulting array of structures (returnArray in this example) and display at will. Just dump the returnArray to see what you have. Like so:
<cfset var returnArray = ArrayNew(1) />
<cfset var entryStruct = StructNew() />
<cfset entryNodes = XmlSearch(statsXML, ‘//entry/’) />

<cfloop from=”1″ to=”#ArrayLen(entryNodes)#” index=”num”>
<cfloop from=”1″ to=”#ArrayLen(entryNodes[num].metric)#” index=”i”>
<cfset “entryStruct.#Mid(entryNodes[num].dimension.XmlAttributes["name"],4,
Len(entryNodes[num].dimension.XmlAttributes["name"]))#” = entryNodes[num].dimension.XmlAttributes["value"] />
<cfset “entryStruct.#Mid(entryNodes[num].metric.XmlAttributes["name"],4,
Len(entryNodes[num].metric.XmlAttributes["name"]))#” = entryNodes[num].metric.XmlAttributes["value"] />
</cfloop>
<cfset arrayAppend(returnArray,duplicate(entryStruct)) />
</cfloop>

<cfdump var=”#returnArray#”>

I used the Mid function to strip off the ‘ga:’ prefix on the dimensions and metrics.


Diego
November 24, 2009

I used your codes using grandcentral as the service name. It gave me the auth key but i was not able to login. any suggestions


jen
November 24, 2009

@Diego
Check Google’s documentation for GrandCentral. I’m not sure you can hit GrandCentral through an API yet. I don’t see it on their list:
http://code.google.com/more/
If Google doesn’t allow access to it through an API, you can’t get to it.


jen
November 24, 2009

@Diego
Great! Go for it. Good luck.

Leave a comment

Why ask?

 

« | »