Credential recovery: Reconnecting Google+ if something goes wrong
In my last post, I discussed tricks for upgrading tokens and upgrading to the Google+ sign-in button. This spurred some discussion on the Google+ post, most notably the question, “When do we get the refresh token?” In turn, I figured it would be a good time to write up a post on recovering the refresh token and explaining how and when you can get it. Let’s start with how you get offline access through the refresh token.
Getting offline credentials – the refresh token
How about some background on offline credentials! As described in the basics of OAuth documentation and elaborated in my blog post on the One-time-Code flow with the Google+ sign-in button, there are two types of tokens:
- The access token, which expires in 1 hour (3600 seconds for you “seconds since the UNIX Epoch” folks).
- The refresh token, which never expires, but can only be used to get access tokens – not to make API calls.
Again, the refresh token can not be used for performing API calls but only can be used to mint an access token for API access. This is useful on the server-side of your web sites because you don’t need to have your user signed in for operations like writing app activities. When performing client-side operations, you will never directly work with the refresh token because the Google client library will automatically refresh its access token.
In order to get a refresh token from the sign-in button, you must first create the markup for the button and include the data attribute, accesstype, set to offline:
<button data-scope="https://www.googleapis.com/auth/plus.login" data-requestvisibleactions="http://schemas.google.com/AddActivity" data-clientId="YOUR_CLIENT_ID" data-accesstype="offline" data-callback="onSignInCallback" data-theme="dark" data-cookiepolicy="single_host_origin"> </button>
When the sign-in callback is returned after successful authorization, the argument passed to it will contain an authorization code. This code can then be exchanged for a refresh token and an access token. The Google+ quickstart samples all show how this is done in various languages. As an example, the following PHP code, from the PHP quickstart, shows how code exchange is performed:
$client = new Google_Client(); $client->setApplicationName(APPLICATION_NAME); $client->setClientId(CLIENT_ID); $client->setClientSecret(CLIENT_SECRET); $client->setRedirectUri('postmessage'); $plus = new Google_PlusService($client); ... // Exchange the OAuth 2.0 authorization code for user credentials. $client->authenticate($code); $token = json_decode($client->getAccessToken()); //verify the token $reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' . $token->access_token; $req = new Google_HttpRequest($reqUrl);
When do you get a refresh token?
The simple answer is “Any time that the user is prompted to authorize your application, the authorization code can be exchanged for a refresh token.” The more complicated answer is… it depends. The reason for the complexity here is that the new Google+ sign-in button automatically signs the user in without prompting them because this is consistent with the user experience that Google wants for users across the web.
The following conditions are examples of what will cause the user to be prompted to authorize your application:
- The user has disconnected your application and then tries again to sign in
- The permissions that the app is requesting have changed
- You explicitly request the user to reauthorize*
*Note This should only be used in credential recovery scenarios, which will be discussed later in the article.
So there you have it, the authorization code can be exchanged to return a refresh token anytime the user sees this:
Now that you know when a user gets a new refresh token, you might wonder when you need to re-get that refresh token.
When do you need to recover credentials?
There are a number of scenarios for causes to need to recover server-stored credentials:
- The user disconnects your app
- The database gets corrupted
- An authorization code is exchanged and does not return a refresh token – this could happen if the user blocks your XHR somehow or errors at just the right moment, then signs in again.
So, let’s assume that the worst has happened and you have an invalid refresh token. When this happens, you will get an error from the API indicating that the authorization tokens are no longer valid. The following examples show how you can simulate these scenarios.
Example 1: The user disconnected the application but the session persisted
As an example of things going terribly wrong, manually disconnect the PHP starter I set up from a previous post by:
- Connecting the starter project demo
- Visiting your apps page on Google+ and disconnecting the application
Example 2: Ruining the stored credentials
For simplicity, I have added another couple of endpoints to the PHP server, the first endpoint will destroy the credentials in the user’s session:
- /ruin – destroy the user’s session
Now that you’ve created a state where the user credentials are bad, you can see that the data is bad by looking at the credentials as is now enabled through the following API calls I added to the server:
- /tokendump – Show the session token, if you ruined the session, this will be empty
- /tokeninfo – Test the access token in the session, if you ruined the session, this will show an error
- Try to perform the authorized request to list visible people – if you have ruined the session, this will return an error
Let’s take a look at the third approach, trying to list the people circled. After attempting the API call, you will see the following response:
Google_ServiceException: Error calling GET https://www.googleapis.com/plus/v1/people/me/people/visible: (401) Invalid Credentials
The 401 error, “invalid credentials”, indicates that something wrong has happened. In this case, the user’s account needs to be recovered and access tokens need to be regenerated.
If you were to perform a check against the tokeninfo endpoint for an invalid access token, you would see an error as follows:
Finally, if the user disconnected their account and you checked a token against tokeninfo, you would see the same error:
Let’s finish with a discussion of how you can recover the user’s credentials.
How do you recover the credentials?
So what do you do!??!! You now know that you need to regenerate the authorization credentials as indicated from a server-side call. If the user disconnected your account (Example 1 from the previous section), the answer is simple: the next time the user signs in, you exchange the authorization code for a refresh token, update your database, and are done. If the user does not sign-in again with your application, you should remove any stored data that must be removed as described in the Google+ Developer Platform policies.
So, let’s say the user hasn’t disconnected but their authorization data is no longer valid. In this more complicated case (Example 2), the user hasn’t disconnected from your application. If the user returns to the site’s main page, only an access token will be returned from the access code. To see this happen:
- /ruin – destroy the user’s session
- /signin.php – this will automatically authorize the user and the code returned will only return an access token
- /tokendump – Test the access token in the session, you can dump the user’s session using this script.
Note There is no refresh token! This is because the consent dialog was not displayed as described in “When do I get a refresh token”.
To address this scenario, you must force the consent dialog to appear so that the user is then prompted for allowing offline access to your application, and then you can exchange the returned authorization code for updated credentials. The following code shows how you can force the consent dialog to render for recovery scenarios:
<button class="g-signin" data-scope="https://www.googleapis.com/auth/plus.login" data-requestvisibleactions="http://schemas.google.com/AddActivity" data-clientId="YOUR_CLIENT_ID" data-accesstype="offline" data-approvalprompt="force" data-callback="onSignInCallback" data-theme="dark" data-cookiepolicy="single_host_origin"> </button>
The following demo recovers the user’s account.
This code, which runs server-side, first removes the invalid session credentials. Next, it forces the approval dialog to appear with the aforementioned markup. Finally, it exchanges the returned code for a new refresh/access token.
Be prepared for the worst and support account recovery on your sites for the rare case in which the credentials you have are bad. You should check that you are getting the refresh token when you expect it in order to make sure that you aren’t storing invalid credentials. If you wanted to be extra careful, you could occasionally audit your credentials database for accounts that have invalid refresh / access tokens for Google+.