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 (Demo no longer available.)
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 (Demo no longer available.)
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.