In trying to wire up Google OAuth integration so that I could access the My Business API I ran into some non-obvious hurdles trying to construct the proper flow between my front end and back end applications.
Background
My application consists of a SPA front end (React) and a Java based back end. I needed the user to authenticate with Google and pick a business location listing they wanted to manage. In order for the backend to access the Google API from scheduled jobs I needed to at least persist a refresh token.
Problem
Google does not suffer from a lack of documentation and examples. In fact, they suffer from the exact opposite in that there are multiple documents talking about the same thing but in different ways. Both Java and JavaScript examples are available that show how to easily complete the OAuth flow if you are exclusively doing it from either the server or client side. What is less obvious is how to get and persist a long lived refresh token when logging in from a SPA. In the end the Sign-in for Websites documentation helped the most but was missing a few key parts.
Frontend
The first thing is to load and configure the Google Javascript client. Since I’m using React I injected a reference to the script via componentDidMount. This was so I could wire up the listeners.
componentDidMount() { var clientId = config.google.clientId; var signin = this.onSignIn; var userchanged = this.onUserChanged; window.gInit = function () { try { gapi.load('auth2', function () { var auth2 = gapi.auth2.init({ client_id: clientId, cookiepolicy: 'single_host_origin' }); auth2.isSignedIn.listen(signin); auth2.currentUser.listen(userchanged); gapi.signin2.render('g-signin2', { scope: 'https://www.googleapis.com/auth/plus.business.manage', longtitle: true, theme: 'dark' }); }); } catch (e) { console.log(e); } }.bind(this); (function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = 'https://apis.google.com/js/platform.js?onload=gInit'; js.async = true; js.defer = true; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'google-jssdk')); }
Generating an offline token authentication code
Once a user is logged in we need to request a long lived refresh token that we can persist for later. The JavaScript client by default will not generate a refresh token for you so you must request one explicitly. Note that this will require your user to re-authenticate with Google.
1. Request an authentication code which can be exchanged for access and refresh tokens.
gapi.auth2.getAuthInstance().grantOfflineAccess({ scope: 'https://www.googleapis.com/auth/plus.business.manage' }).then(res => { //make call to backend with the authorization code this.saveToken(res.code); });
Backend
2. In the back end (Java) application, convert the authorization code into a refresh token. To do this the Google Java API provides GoogleAuthorizationCodeTokenRequest. The trick here is that for the redirect url you must pass ‘postmessage’. This is not obviously documented anywhere and I found it via StackOverflow. Using anything else was resulting in redirect_uri_mismatch errors.
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest( this.httpTransport, this.jsonFactory, "https://www.googleapis.com/oauth2/v4/token", clientSecrets.getDetails().getClientId(), clientSecrets.getDetails().getClientSecret(), authCode, "postmessage") .execute(); return tokenResponse.getRefreshToken();
The refresh token was then persisted for later.
3. Use the refresh token to generate an access token when we want to call into the API.
TokenRequest tokenRequest = new GoogleRefreshTokenRequest( this.httpTransport, this.jsonFactory, refreshToken, clientSecrets.getDetails().getClientId(), clientSecrets.getDetails().getClientSecret() ); TokenResponse tokenResponse = tokenRequest.execute(); return tokenResponse.getAccessToken();