Raymond Camden's Blog Rss

Working with Google and OAuth2

24

Posted in ColdFusion | Posted on 12-06-2011 | 3,576 views

Warning: What follows should NOT be considered a guide. What follows is what worked for me after struggling to get things right. I do not understand this 100%. My only hope is that it may help others. Please take with large portion of salt.

Last night I began work on an update to my Google Calendar API to make use of the latest version of their API. In order to use this version, I had to switch to using OAuth. I've done a tiny bit of OAuth in the past, but never OAuth 2. I did a bit of digging into their docs and was able to get a working version. Here's what I came up with.

The first thing you have to do is create an "Application" registered with Google. This is done via their API Console and for the most part is a painless process. You begin by creating a new application.

Notice that - initially - you can't edit the Redirect URI. We're going to fix that in a second. Click "Create Client ID." When the page reloads, click to edit and change the redirect URI to be a real CFM. Note - you can use localhost here and it works fine. Just be sure to change it to http.

Close that dialog by hitting update. Back on the application list, make note of your client ID and client secret. Your code is going to need this. I set up a basic Application.cfc to store these in the Application scope:

view plain print about
1component {
2    this.name="googledocs3";
3    this.sessionManagement=true;
4
5    public boolean function onApplicationStart() {
6        application.clientid="it's my pin, really";
7        application.clientsecret="iwritephpatnight";
8        application.callback="http://www.coldfusionjedi.com/demos/2012/dec/6/callback.cfm";
9        return true;
10    }
11
12}

Ok, so in order to start the OAuth process, we have to link to Google. The link to Google will include your client id, your redirect or callback url, and a scope. The scope is what you want to use at Google. Each service will have it's own scope. Here's the link I use for my demo:

view plain print about
1<cfset authurl = "https://accounts.google.com/o/oauth2/auth?" &
2     "client_id=#urlEncodedFormat(application.clientid)#" &
3     "&redirect_uri=#urlEncodedFormat(application.callback)#" &
4     "&scope=https://www.googleapis.com/auth/calendar&response_type=code"
>

5
6<cfoutput>
7authurl=#authurl#<p>
8<a href="#authurl#">Login</a>
9</cfoutput>

I output it just so I can see what it looks like a bit easier. Note - most sites use a little popup window. That would work fine. The response_type=code is what you use for server side applications. At this point, you can start testing. If you click that link, you end up on a page like this:

At this point, if the user clicks "Allow access", Google is going to send you to your callback URL. In the URL will be a variable code. Now here comes the tricky part. Google sent you back a code. That code is like a ticket to ride. You need to give that code back to Google in order to get a token. The token is the real thing you want. I found this blog entry which nicely wraps up the call in a UDF:

view plain print about
1<!---
2http://www.sitekickr.com/blog/http-post-oauth-coldfusion
3--->

4<cffunction name="getAccessToken">
5 <cfargument name="code" required="false" default="" type="string">
6 <cfset var postBody = "code=" & UrlEncodedFormat(arguments.code) & "&">
7 <cfset postBody = postBody & "client_id=" & UrlEncodedFormat(application.clientid) & "&">
8 <cfset postBody = postBody & "client_secret=" & UrlEncodedFormat(application.clientsecret) & "&">
9 <cfset postBody = postBody & "redirect_uri=" & UrlEncodedFormat(application.callback) & "&">
10 <cfset postBody = postBody & "grant_type=authorization_code">
11 <cfhttp method="post" url="https://accounts.google.com/o/oauth2/token">
12 <cfhttpparam name="Content-Type" type="header" value="application/x-www-form-urlencoded">
13 <cfhttpparam type="body" value="#postBody#">
14 </cfhttp>
15    <cfreturn deserializeJSON(cfhttp.filecontent.tostring())>
16</cffunction>
17
18<cfset session.token = getAccessToken(code)>
19<cfdump var="#session.token#">
20
21<a href="test.cfm">TEST</a>

The result is that now you have a session token. That session token gives you access to the scope you requested earlier. Here is what that token looks like:

There are three very important things here:

  1. First, the access_token is the key to using the services. You want to remember that in the session scope.
  2. Second, it doesn't last forever. You can see the timeout there.
  3. Third, the refresh_token, however, does last. (Forever, no. I think it lasts until the user blocks your app's access.) This I think you will want to store in a persistent location.

So given the token, you can now start hitting the API. So for example, to get a list of calendars...

view plain print about
1<cfhttp url="https://www.googleapis.com/calendar/v3/users/me/calendarList">
2    <cfhttpparam type="header" name="Authorization" value="OAuth #session.token.access_token#" >
3</cfhttp>

Google's Calendar API is REST based, so basically, you just formulate the URL right and pass along the token via an Authorization here. You get nice JSON back so it's pretty easy to work with. If you run my demo (link will be below), and if you actually are a Google Calendar user, you should get a list of your calendars. I tested a few other parts of the API and it all works rather nicely.

Now I mentioned above that the token does not last forever. Remember that 'refresh' token you got? You can request a new access token using a modified form of the earlier blogger's UDF:

view plain print about
1<cffunction name="getRefreshToken">
2 <cfargument name="refresh" required="false" default="" type="string">
3 <cfset var postBody = "client_id=" & UrlEncodedFormat(application.clientid) & "&">
4 <cfset postBody = postBody & "client_secret=" & UrlEncodedFormat(application.clientsecret) & "&">
5    <cfset postBody = postBody & "refresh_token=#arguments.refresh#&">
6 <cfset postBody = postBody & "grant_type=refresh_token">
7 <cfhttp method="post" url="https://accounts.google.com/o/oauth2/token">
8 <cfhttpparam name="Content-Type" type="header" value="application/x-www-form-urlencoded">
9 <cfhttpparam type="body" value="#postBody#">
10 </cfhttp>
11    <cfreturn deserializeJSON(cfhttp.filecontent.tostring())>
12</cffunction>

Notice that this just slightly tweaks the values sent. In my testing, a call to this refresh service worked fine. I was able to get a new access token after the last one expired. You can try this yourself using the button below. I hope this code is helpful to others.

Comments

[Add Comment] [Subscribe to Comments]

When you click the link for "TEST" and goes to test.cfm it throws an error.
It would help if I uploaded the file. Done.
I just did a whole suite of tools for a client. Took a while to get it running. The api spec is typically loaded with tons of developer assumptions about the audience. But once you make 100 or so mistakes the flow starts to make sense. I am in the process of scoping out all big google apis to oauth 2. Feel free to contact me for any hints.
@Chris - I would like to get some information, you can add me on Google+ here to talk: https://plus.google.com/u/0/109478969095694584913
Good stuff (as usual). The whole "get ticket, then send ticket back to get token" routine is pretty much the same deal as what I have to do to connect my ColdFusion apps to our campus Jasig CAS (Common Authentication System) implementation.
I'm not sure what's going on with Oauth 2 over at Google (https://accounts.google.com/o/oauth2/token) but tonight, we are no longer receiving the refresh_token. It seems to be replaced with something called id_token.

My client noticed it this afternoon and about 8PM tonight I saw it.

Ray, to check, cfdump cfhttp.filecontent in method getAccessToken and see if the refresh token has gone away. No notes out there on Google about it.

I should note that i wrote my own a few weeks back, but its basically the same concept.
Not sure. My Googling didn't find anything definitive. It does seem to be related to OpenID. That's the best I can say. In my testing last night, I did not see this.
I started a thread: https://groups.google.com/forum/#!topic/oauth2-dev...

I already have received one reply that validates my discovery and offers some insite.
You mind posting back here when you get a final resolution?
Monday, November 14, 2011
Upcoming changes to OAuth 2.0 endpoint

By Justin Smith, Senior Product Manager

http://googlecode.blogspot.com/2011/10/upcoming-ch...
if you concat "&access_type=offline&approval_prompt=force" to the end of your login link, the Oauth process works exactly as it did before they changed the API.
Wow, thank you. And note:

UPDATE 11/14: After considering the feedback and timing, we have decided to delay the launch of the changes described in this post. The new date for these changes is December 7th, 2011. Once again, we expect these changes to have minimal impact. If you have any questions or comments, please post on the OAuth 2.0 Group. We will be actively monitoring that group and will work to respond quickly.
Please feel free to check out my Google Oauth2 harness for ColdFuion

The oAuth 2.0 Connector for ColdFusion 8 and 9 allows developers to quickly integrate the Google oAuth 2.0 API to their site. Available modules include: oAuth API connector, Contacts API connector, and Calendar API connector. Developers can view authenticated user's basic contact information, upload an event, view contact lists, add contacts and view calendar metas.

Great for kick starting your jQuery Mobile or Flash Builder/Android application.

http://www.appwise.org/google/oauth/index.cfm
I'm trying to integrate with WePay (who uses OAuth2) and I can't get the Authorization header to work. According to the WePay documentation, it needs to be send in the following format:
Authorization: Bearer <access-token>

I've tried several permutations of this with cfhttpparam, but none of them work. Has anyone tried this before? Am I missing something stupid?

I've tried

<cfhttpparam type="header" name="Authorization" value="Bearer #Application.WepayToken#" />

<cfhttpparam type="header" name="Authorization" value="Authorization: Bearer #Application.WepayToken#" />

<cfhttpparam type="header" name="Authorization" value="Bearer:#Application.WepayToken#" />

I'm pretty sure by the time I get this working, I won't have any hair left. If anyone has any ideas or thoughts, please let me know.

Thank you
Not quite sure what you're doing but here is how I return the bearer token (arguments.access_token) to Google in their oAuth2 process.
<code>
      <cfhttp method="get" url="https://www.google.com/m8/feeds/contacts/#argument...(arguments.latestDate,'yyyy-mm-dd')#T00:00:00" result="latestContacts">
   <cfhttpparam type="header" name="GData-Version" value="3.0">
<cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#arguments.access_token#">
   
</cfhttp>
</code>
According to the manual (https://stage.wepay.com/developer/tutorial/api_cal...)

you should try:

<cfhttpparam type="header" name="Authorization" value="Bearer #Application.WepayToken#" />
Thanks Christopher. There must be something else going one because that was the first combination I tried.

<cfhttp url="#Application.WepayApiUrl#" method="post">
<cfhttpparam type="header" name="Authorization" value="Bearer #Application.WepayToken#" />
</cfhttp>

I've tried as a post and as a get, but neither seem to make a difference. Each time it returns a statuscode 501.

At this point, I'm guessing the problem lies somewhere other than the cfhttpparam tag, so it's probably beyond the scope of this blog post. If I find an answer, I'll post it for those with morbid curiosity.
Well...

It looks like the default example they have shown to return your user info does not work, but the other API calls work just fine. So there was nothing wrong with what I was doing, but instead something with that particular example. Now I've got a basic model of an interface to their API working.

Thanks for the advice.
Is your oauth 2.0 / coldfusion / gcal api project on hold or forthcoming ?
On hold just because I do not have the time. If I need to github this so you can fork it/add to it, let me know.
I'm a beginner. No need to github it. I've been trying to implement on my site. Just hoping to peruse your code and glean what I could from it. Many thanks though.
Looks like I did not post the demo. Best I can suggest is using the code in the example above.
Thanks for this Ray! I've got the auth working and can use token to hit API. Could you suggest the best way to automagically retrieve a new access token using a refresh token stored in a db when the access token expires?

Thx!
I don't have an example of it handy - but I believe you pass the refresh token back to the API. I remember playing with it back when I did this blog post. It _should_ be simple. (Speak up if not.)

[Add Comment] [Subscribe to Comments]