Google OAuth2 Offline Access in Single Page Apps

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();
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s