Twitter: raymondcamden


Address: Lafayette, LA, USA

Google+ Sign-In and ColdFusion

02-20-2014 6,647 views JavaScript, ColdFusion 12 Comments

A while ago I blogged (see related entries) about ColdFusion, OAuth, and Google. I ended up using this on a client's project. They go to his app, click sign-in, are redirected to Google, and upon authenticating, are brought back to the app so that profile information can be retrieved from Google and synced up with a local user record. My client then asked me to take a look at Google+ Sign-In. I spent some time working on some code and I thought I'd share. While this post uses ColdFusion for the back end, I think it could be helpful to folks using other back ends as I found some issues that apply universally.

The docs describe three different ways to use the API, but one isn't encouraged so really there are only two. The first is a client-side only approach. As you can imagine, this is done via HTML and JavaScript. While limited (and I'll talk about why in a second), Google gets credit for making it real easy to use (mostly).

As with the OAuth examples I shared before, you need to create an app in their developers console. Once you do, you can then create your HTML page and try out their guide. There are a few ways of adding a sign-in button, but the simplest is with just a HTML button as described here. You drop in some JavaScript code and then layout your button with pure HTML.


<span id="signinButton">
  <span
    class="g-signin"
    data-callback="signinCallback"
    data-clientid="1094453904134.apps.googleusercontent.com"
    data-cookiepolicy="single_host_origin"
    data-requestvisibleactions="http://schemas.google.com/AddActivity"
    data-scope="https://www.googleapis.com/auth/plus.login">
  </span>
</span>

As you can see, data attributes are used to drive how the button behaves, which is a wonderfully simple way of doing things. I dig that. You have to write the code that handles the callback but Google provides an example of what that looks like. You can handle the result any way you wish, but one probable use case will be to grab the user's profile so you know who they are.

Loading the profile can be done if you load in the appropriate library. Google demonstrates this with this example:

gapi.client.load('plus','v1', callback);

Unfortunately I was never able to find the docs for the library. You have access to it via gapi.client.plus so I simply dumped that and then used the HTTP guide for reference. Oddly, the JavaScript reference focused on the button API (you can create the sign-in button with JavaScript instead of HTML) and didn't talk about the other stuff you would do after sign-in. That seems pretty weird to me, but I was able to get something working. I built a quick demo that logs you in, gets and displays your profile, and gets your posts. I didn't display the posts, but if you open up console you can see it being returned. The code isn't the prettiest, but take a look:

http://www.raymondcamden.com/demos/2014/feb/20/test1.html

Ok.... so that works but is missing something. While you could use this to build your own G+ viewer, you can't use it to sign users into your application. Why? The authentication is entirely client-side, how would you tell your server? Well, you could simply ping the server with an XHR call saying, "Hey, I'm Ray", but how would you know that that call was authentic? People like me love to hit your site with devtools open and see what mischief we can get up to.

This is where the second approach comes in - the hybrid server-side flow. This approach basically has your client-side code do an authentication, take a code value, pass it to the server, and have the server hit Google to see if the code is valid. I'm going to, um, "borrow" from their docs and share this image, which demonstrates it.

Unfortunately, the docs kinda break down here. First, they focus on PHP, Java, and Python, which means if you are using something else you're out of luck since in all three cases they are using a library to hide many of the implementation details. Secondly, some of the examples are out of date (like in the PHP area for example) and I didn't realize that until a lucky search turned up a bug report.

This is what I figured out, and I should point out that I'm not entirely confident this is 100% right, but it seems to work. First - the hybrid process asks that after you login via the client-side, you pass a code value to the server. This is an example:


$.post("auth.cfc?method=store&returnformat=json", {code:authResult['code'],state:'#session.state#'}, function(res, code) {
		    	console.log(res);
},"JSON");

authResult was passed in via Google and session.state is simply a state verification token much like what I used in the OAuth demos. On the server side I discovered I could use the same code I had used before to request an access token:

//Credit: http://www.sitekickr.com/blog/http-post-oauth-coldfusion
private function getGoogleToken(code) {
	var postBody = "code=" & UrlEncodedFormat(arguments.code) & "&";
		 postBody = postBody & "client_id=" & UrlEncodedFormat("1094453904134.apps.googleusercontent.com") & "&";
		 postBody = postBody & "client_secret=" & UrlEncodedFormat("oops this is secret") & "&";
		 postBody = postBody & "redirect_uri=" & UrlEncodedFormat("postmessage") & "&";
		 postBody = postBody & "grant_type=authorization_code";


	var h = new com.adobe.coldfusion.http();
	h.setURL("https://accounts.google.com/o/oauth2/token");
	h.setMethod("post");
	h.addParam(type="header",name="Content-Type",value="application/x-www-form-urlencoded");
	h.addParam(type="body",value="#postBody#");
	h.setResolveURL(true);
	var result = h.send().getPrefix();
	return deserializeJSON(result.filecontent.toString());

}

remote function store(code, state) {
	if(arguments.state != session.state) {
		throw new exception("Invalid state");
	}
	session.code = arguments.code;

	var auth = getGoogleToken(session.code);
	session.auth = auth;
	return auth;
}

Note that my CFC returns the entire auth object, which is probably unnecessary. If this works, I store the authentication info in the session. I confirmed this worked by adding a call (via http in the CFC) to get the profile and it worked fine. So at this point, I've got a client side app with access to the Google+ profile and the server has a temporary token to also access the user's information. I could then say - on the server - that I "know" who the user is. (Oh, one important note: see the redirect_uri is set to postmessage.)

You can run this demo here: http://www.raymondcamden.com/demos/2014/feb/20/test2.cfm

I've included a full zip of the demo code below. As I said, the code isn't pretty but hopefully it can help others build something decent with Google+ Sign-In.

Download attached file

Related Blog Entries

12 Comments

  • Commented on 02-22-2014 at 1:11 PM
    Nice work Ray, took a minute to read through all the documentation; but well worth it. At this point in time I have already created my own secure sign-in system and not sure if I can find use for this just yet. Will have to place some thought into it before I actually try incorporate this into a site.
  • Misty #
    Commented on 03-09-2014 at 11:55 AM
    So what is the basic difference between google+ and google login, is the google login going to get depreciated in near future, i have read it somewhere in google developer page.

    What exactly it differs from if we implement this http call to get user profile and authenticate info:

    <cfhttp url="https://www.googleapis.com/oauth2/v1/userinfo"...; result="userInfo" method="get" resolveurl="true">
             <cfhttpparam type="header" name="Authorization" value="OAuth #application.google_access_token#">
             <cfhttpparam type="header" name="GData-Version" value="3">
          </cfhttp>
  • Commented on 03-09-2014 at 12:05 PM
    "So what is the basic difference between google+ and google login"
    Google login is based on a GMail login or Google for Apps. Google+ is the same, but in theory I think you can have a G+ signin w/o GMail. Not entirely sure on that though.

    "Is the google login going to get depreciated in near future, i have read it somewhere in google developer page"

    Um - I don't know. It sounds like you know more than me.

    I don't understand your last question. To be clear, the information you get back on profile is based on the scopes you request. All of the OAuth demos I shared here follow that pattern. You need to check their docs to see what fields are returned and what you have to ask for to get additional fields.

    As I said elsewhere, try cfdumping the result and see all the fields. Don't see what you need? Then check the docs and see what scopes you need to add.
  • Misty #
    Commented on 03-09-2014 at 12:19 PM
    Hi Ray, Finding more with google Developer Console, Here is the ink I was talking about

    https://developers.google.com/+/api/auth-migration...

    Another Question: The Demo you shared for Google Plus Login Consist of Login via client side, is that the only way of Google+ signin as of now.

    I shall be very thankful if you can share the tag type code of the above cfscript, I know that code, but love the cf way.

    Is there any performance based you always prefer script based
  • Commented on 03-09-2014 at 12:22 PM
    "Another Question: The Demo you shared for Google Plus Login Consist of Login via client side, is that the only way of Google+ signin as of now. "

    Yes - note on top where I say there are 3 paths to using the auth - and one is server-side only.

    "I shall be very thankful if you can share the tag type code of the above cfscript, I know that code, but love the cf way. "
    Um, it is CF. :) I don't have any plans on rewriting it as tags. You should be able to do this yourself. If you have a specific question about how a particular script thing maps to tags, ask.
  • Misty #
    Commented on 03-09-2014 at 1:01 PM
    Ok, I tried making it work, but in my case, it is neither opening popup and always showing as:

    Sign-in state: immediate_failed

    Code i changed in auth.cfc

    private function getGoogleToken(code) {
          var postBody = "code=" & UrlEncodedFormat(arguments.code) & "&";
              postBody = postBody & "client_id=" & UrlEncodedFormat("#application.google_client_id#") & "&";
              postBody = postBody & "client_secret=" & UrlEncodedFormat("#application.google_secretkey#") & "&";
              postBody = postBody & "redirect_uri=" & UrlEncodedFormat("postmessage") & "&";
              postBody = postBody & "grant_type=authorization_code";
  • Commented on 03-09-2014 at 8:24 PM
    Can you tell me what you changed in getGoogleToken?
  • Misty #
    Commented on 03-09-2014 at 10:38 PM
    I added my Own Google Secret Key and Client ID to match whatever i have specified in the developer console

    This is what i changed

    #application.googleclientid#

    #application.googlesecretkey#
  • Commented on 03-10-2014 at 6:17 AM
    It may be that you need to enable G+ in your apps dashboard. Outside of that I do not know. If you have it online where I can hit, I can try to take a look.
  • Commented on 05-06-2014 at 7:46 PM
    @Misty, if you are still struggling with this, note you have to change to client id value in auth.cfc and test2.cfm. I missed it in test2.cfm at first and got the same message you reported.
  • Sean Ford #
    Commented on 05-21-2014 at 6:49 PM
    HAve you attempted to get the new cfOauth tags working in CF11 yet? I gave it a shot with the beta, but had no luck. About to try again with the release version, but was hoping to come across a proof of concept or some guidance before I dive in.
  • Commented on 05-22-2014 at 7:48 AM
    Nope, I haven't played with that yet. I wanted to so I could compare.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty