Search code examples
javascriptreactjsoauth-2.0google-oauth

Keeping user logged in after refresh/using refresh token with Google OAuth2 in React app


I’m building a React app where a key part of the functionality is a user can sign into their Google account and then access a feed of their most recent Google Drive/Docs mentions and notifications. A user arrives at my site where I load the Google OAuth2 client with my client_id, apiKey, scope and discoveryDocs, and they can click a button to sign in. For convenience, I’d like the user to not have to re-login and re-auth with their Google account every time they use the app or the app refreshes, I’d like the login information to be saved across sessions. For this I’ll use localStorage to start but eventually integrate a database like Firebase.

After looking through the JavaScript client Google OAuth2 docs I understand how most things work - understand the data and methods stored in the GoogleUser, GoogleAuth, etc objects. I’m having a little trouble with access and refresh tokens. I recognize that you can get the authenticated user’s information through gapi.auth2.getAuthInstance().currentUser.get() and gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse() returns an object with a lot of what I think I need like id_token, access_token and metadata like expires_at and token_type. I also see the grantOfflineAccess() method from which I extract response.code, but I’m not quite sure which of these tokenized strings is the right one to use and how I need to use it.

This FAQ from Google (https://developers.google.com/api-client-library/javascript/help/faq) is somewhat helpful but advises to Refresh the token by calling gapi.auth.authorize with the client ID, the scope and immediate:true as parameters., but gapi.auth.authorize is noted by Google in the client JS OAuth2 library as being incompatible with the more widely used and heavily documented api.auth2.init and signIn.

I also have a vague idea from posts like Google OAuth2 API Refresh Tokens that I need to follow server-side OAuth2 instructions and I can only get this refresh_token through a server-side call, but I’m still at a bit of a loss. I’ll caveat and say I’m more of a front end developer/designer so I'm shaky on my node and server-side skills.

TL;dr: I don't know how to keep my users who signed in via Google OAuth2 signed in after a refresh. I have an idea it's due to refresh_token and access_token and I have access to them but I don't know what to do after that, in terms of sending data to Google servers, getting information back, and setting the token information for the given user when they return.

Here's my method that calls on componentDidMount (basically when my app first loads):

loadGoogleClient = () => {

    gapi.load("client:auth2", () => {
        gapi.auth2.init({
          'client_id': my-client-id,
          'apiKey': my-key,
          'scope': "https://www.googleapis.com/auth/drive.readonly",
          'discoveryDocs': ['https://content.googleapis.com/discovery/v1/apis/drive/v3/rest']
        })

            // Listen for sign-in state changes.
        console.log(`User is signed in: ${gapi.auth2.getAuthInstance().isSignedIn.get()}`);
        gapi.client.load("https://content.googleapis.com/discovery/v1/apis/drive/v3/rest")
            .then(() => { console.log("GAPI client loaded for API"); 
                }, (error) => { console.error("Error loading GAPI client for API", error); 
            });
    console.log('Init should have worked');
    });
}

And here's my code that's onClick on my Signin button:

authGoogle = () => {
    gapi.auth2.getAuthInstance()
            .signIn({scope: "https://www.googleapis.com/auth/drive.readonly"})
            .then(function() { console.log("Sign-in successful"); },
                  function(err) { console.error("Error signing in", err); });
}

Solution

  • If you are using the client lib (the gapi api) there is no need for a refresh token... Once logged in it should persist across sessions and refreshes... The issue is the code...

    1) Include this in your index.html in the head section:

    <script src="https://apis.google.com/js/api.js"></script>
    

    2) Here is a component that will handle auth using the gapi lib and render a button conditionally (The code is self-explanatory but if you have a question just ask...)

    import React from 'react';
    
    class GoogleAuth extends React.Component {
      state = { isSignedIn: null };
    
      componentDidMount() {
        window.gapi.load('client:auth2', () => {
          window.gapi.client
            .init({
              clientId: '<your client id here...>',
              scope: 'email', // and whatever else passed as a string...
            })
            .then(() => {
              this.auth = window.gapi.auth2.getAuthInstance();
              this.handleAuthChange();
              this.auth.isSignedIn.listen(this.handleAuthChange);
            });
        });
      }
    
      handleAuthChange = () => {
        this.setState({ isSignedIn: this.auth.isSignedIn.get() });
      };
    
      handleSignIn = () => {
        this.auth.signIn();
      };
    
      handleSignOut = () => {
        this.auth.signOut();
      };
    
      renderAuthButton() {
        if (this.state.isSignedIn === null) {
          return null;
        } else if (this.state.isSignedIn) {
          return <button onClick={this.handleSignOut}>Sign Out</button>;
        } else {
          return <button onClick={this.handleSignIn}>Sign in with Google</button>;
        }
      }
      render() {
        return <div>{this.renderAuthButton()}</div>;
      }
    }
    
    export default GoogleAuth;
    

    Now you can simply use this component/button anywhere in your app... Meaning if you have a Navigation component simply import it there and use it as a button login / log out...