2014-11-16

This is the second post in my series about Google-plus OAuth flows, in which I talk about how to implement the initial step (the client-side step) in a Hybrid flow, and finally how to sign-out a user who has logged into your site using Google-plus (this can be trickier than you might expect if your logout button is on a page where you don't want to include a magical Google sign-in button).

The login page with a custom google sign-in button

First include jQuery (not necessary but makes my life simpler), and the google javascript API (you can read about this here), I'm choosing the "Adding the sign-in button to your page with JavaScript" client-side flow for initial step of a Hyrbid flow (you could just as well add the HTML button or "Initiate flow with javascript"):

You can see the ?onload=render parameter in the gapi script, and this will call the render function when the page loads, so let's define this:

, here you need to replace YOUR_CLIENT_ID (register your application at the google developers console to grab this) with your own and SCOPES with the scopes you are interested in accessing for your users, e.g. https://www.googleapis.com/auth/plus.login email (note this single string is a whitespace separated list of the scopes).

Let's go through the other parameters one by one. Firstly, customBtn is your buttons id in the DOM (we'll define the html for the button soon) where the button will be rendered. Next signInCallback is the function that gets called after the user is signed in (this is instead of the usual OAuth redirection URI), more on that later.

Next, cookiepolicy: google stores various cookies on your user's computer, for example the signed-in status of the user to your site, which saves them having to query their servers for this data (you can read in detail about it here).
Note these cookies are distinct from the cookies which specify that the user is logged into Google itself (e.g. via Gmail). In fact if the user is logged into Google in another browser and visits your site, they may be automatically signed in (since the user status cookie telling gapi that the user is signed out of your site is missing and Google will check its servers). In fact you can test this by playing about with the G_USERSTATE_S4 cookie. The value of this cookie should look something like

when the user is signed-in to your site, and like

when they are signed out. Try tweaking its value using something like Firebug, or try clearing it and see what happens when you visit the login page.

The cookie policy specifies the domains that can access the cookie. If your website has only a single domain and no sub-domains, single_host_origin is the way to go. Note if you set this to none , gapi.auth.signOut() may not work, and it will also may not work on localhost for testing and development, unless you map the domain in /etc/hosts appropriately, i.e.

Finally accesstype: offline: if you want to be able to access the user's data when they are not present, e.g. if you want to be able to get a refresh token to do server side requests, as indeed we will for our hybrid flow.

To define the HTML for the button itself all we need is

, which you can style however you like.

As for the callback:

Obviously you can customize this however you like. In the above code, I simply note if the immediate_failed error occurs; Google contacts the server to see if they are already logged into your site, if they are there is no need for them to re-sign in and they are logged in automatically, if it's the first time a user encounters the login page then obviously they are not already signed-in and the immediate failed error returns. If the user has signed-out of your site with google-plus previously then the user_signed_out error will be returned to the callback. Finally if non of the other above errors, then the user has successfully signed in, so grab the access_token and do what you require.

In my case I am using python-social-auth on the server side, so I populate a hidden form and fire the submit sending the one time authorization code to the appropriate python-social-auth view that can then exchange that authorization code for an access token and gain acess to the users data (i.e. complete the last leg of the hybrid flow on the server side now that the client side has finished the initial legwork).

Django provides us with a {% csrf %} token, which saves us having to generate/verify it manually, but if your framework doesn't provide this take a look at step 2 and 7 of https://developers.google.com/+/web/signin/server-side-flow

You might be wondering why we provide our server with the browser access token given that the plan is for it to use the code (authorization code) to go grab its own access token. I'm not 100% sure (in the google examples scripts, Step 6, only the code is passed to the server via AJAX), but from the python-social-auth source code, see here and here that handles the social:complete, it looks like the original access_token is first being used to verify there were no problems in the interim such as user denying access. You might also be wondering why not just send the access token to the server and let it use that for requesting the user data and be done with it; the key is that the server can use the authorization code along with its secret key (unlike the browser based js) to obtain an all powerful refresh token (at least the first time), which it can use to obtain a limitless supply of access token as they expire every 3600s.

Signing-out

If your sign-out link is on the same page as your sign-in link you need to do nothing more than call

However, if you want to sign the user out of some page where you don't want a sign-in button (perhaps a user profile page), then it appears you have two choices

Repeat the above code to render a sign-in button, but use CSS to make it hidden.

Call the gapi.auth.signIn or gapi.auth.authorize functions yourself first before calling gapi.auth.signOut.

Manually calling authorize method

These config options are similar to those we met in render, yet notice the underscore in client_id now. The key extra settings is 'response_type' : 'token id_token'. Adding id_token to the response type requests means we get the id_token which tells us which user is to be signed out by the signOut method. (If we had used the signIn method that would have been added by default.)
Most importantly note the immediate:true line; without this authorize briefly pops up a white dialog box before it realizes that the user is already logged in and before the user is signed out, slightly annoying. Even worse though, what if your user is not signed into your site via google, but just by a standard account or by another social auth platform like Facebook? Then the white box popup will prompt the user to sign-in in to Google, which would be very odd behaviour when they are trying to sign out of your site, and moreover when they may not even have a Google plus account! Adding immediate:true does the same behind the scenes login attempt that the magical Google sign-in button attempts when it first loads without showing a UI pop-up.

Now simply call authorize:

where we define the callback this time as

The first time authorize calls the callback (providing there is no error) the first if condition is met, and signOut is called. Note that signOut then also calls the callback signInCallback but this time it passes the user_signed_out error, so signOut will not be fired again if all went well.

I found it was important do the redirect to your pages normal logout view within the callback, as oppose to say underneath the gapi.auth.authorize in the your sign-out buttons onclick script, since it seems that sometimes the window.location.href line would execute before the signInCallback had finished (Something async going on in the gapi script??).

Render hidden google sign-in button method

Another option is to render a sign-in button (just like on the login page) but make it hidden. When the sign-in button loads it will initially do a check (behind the scenes with no popup) to see if the user is already signed in or not and this is the key. If they are not signed-in to your site with Google-plus, the "error" immediate_failed is passed to the callback, and if they are are then great, you will now be good to use gapi.auth.sgnOut(); with no problems.

Thus we have the hidden button html

, the render function (more or less just like on the sign-in page)

and finally some callback:

Again you could add your own functionality to the callback for each of the various cases.

Show more