6

How do I get Firebase Auth to work with Google drive api?

Using react, I have firebase auth working and google drive (gapi) both working individually but am struggling to merge the two.

With Firebase 9, it's been made easier to access Google API: This gives you a Google Access Token. You can use it to access Google APIs.

My thinking is to get the token and then pass that to the gapi .init function. However, I'm not sure where to go after that. Any guidance in the right direction would be greatly appreciated!

import { getAuth, getRedirectResult, GoogleAuthProvider } from "firebase/auth";

onst auth = getAuth();
signInWithPopup(auth, provider)
  .then((result) => {
    // This gives you a Google Access Token. You can use it to access the Google API.
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const token = credential.accessToken;

My gapi implementation:

useEffect(() => {
    window.gapi.load("client:auth2", initClient);
  }, []);

  useEffect(() => {
    if (authState) {
      authState.isSignedIn.listen(updateSigninStatus);
    }
  }, [authState]);

  const initClient = () => {
    try {
      window.gapi.auth2
        .init({
          apiKey: "API KEY...",
          clientId:
            "CLIENT ID...",
          scope: "https://www.googleapis.com/auth/drive",
          discoveryDocs: [
            "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
          ],
        })
        .then(() => {
          const authInstance = window.gapi.auth2.getAuthInstance();
          setAuthState(authInstance);
        });
    } catch (err) {
      console.log(err);
    }
  };

  const signInFunction = () => {
    authState.signIn();
    updateSigninStatus();
  };

  const signOutFunction = () => {
    authState.signOut();
  };

  const updateSigninStatus = () => {
    setSigninStatus();
  };

  const setSigninStatus = async () => {
     //code...
      }
    }
  };
InquisitiveTom
  • 423
  • 3
  • 13

2 Answers2

2

I was struggling with the same thing and then I heard the Fireship guy talking about how easier it was to do it the other way around - sign in with gapi and pass credentials to Firebase.

I'm gonna share the way I did it, so you can relate to the solution and apply it to your project.

Initiate gapi using gapi-script in a gapi.js file and export gapi instance which you will use throughout the app.

import { gapi } from 'gapi-script'

gapi.load('client:auth2', async () => {
  gapi.client.init({
    apiKey: <YOUR_API_KEY>,
    clientId: <YOUR_GAPI_CLIENT_ID>,
    scope: 'https://www.googleapis.com/auth/drive.metadata.readonly',
    discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
  })

  gapi.client.load('drive', 'v3', () => {})
})

export default gapi

Then initiate Firebase in a separate file as follows:

import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: <YOUR_FIREBASE_API_KEY>,
  authDomain: <YOUR_FIREBASE_AUTH_DOMAIN>,
  projectId: <YOUR_FIREBASE_PROJECT_ID>,
  storageBucket: <YOUR_FIREBASE_STORAGE_BUCKET>,
  messagingSenderId: <YOUR_FIREBASE_MESSAGING_SENDER_ID>,
  appId: <YOUR_FIREBASE_APP_ID>,
  clientId: <YOUR_FIREBASE_CLIENT_ID>,
  scopes: ['email', 'profile', 'https://www.googleapis.com/auth/drive.metadata.readonly'],
  discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
}

const firebaseApp = initializeApp(firebaseConfig)
const firebaseAuth = getAuth(firebaseApp)

export { firebaseApp, firebaseAuth }

And finally, in the auth.js file you can define functions that will be used throughout the project. You will notice that after logging in id_token is being grabbed from the auth response and passed as a credential to GoogleAuthProvider.

import { GoogleAuthProvider, signOut, signInWithCredential } from 'firebase/auth'
import { firebaseAuth } from './firebaseConfig'
import gapi from '../gapi'

/**
 * Sign in using gapi and pass credential to Firebase
 * Needs to be configured in https://console.firebase.google.com/u/0/project/<YOUR_PROJECT>/authentication/providers
 * by passing Web SDK Configuration (Web client ID and Web client secret) which can be found on
 * https://console.cloud.google.com/apis/credentials?project=<YOUR_PROJECT> under OAuth 2.0 Client IDs
 */
const signInPopup = async () => {
  try {
    const googleAuth = gapi.auth2.getAuthInstance()
    const googleUser = await googleAuth.signIn()

    const token = googleUser.getAuthResponse().id_token
    const credential = GoogleAuthProvider.credential(token)

    const response = await signInWithCredential(firebaseAuth, credential)

    // store user
  } catch (error) {
    // clean user from store
    console.error('signInPopup (firebase/auth.js)', error)
  }
}

const signOutUser = async () => {
  try {
    await gapi.auth2.getAuthInstance().signOut()
    console.log('User is signed out from gapi.')

    await signOut(firebaseAuth)
    console.log('User is signed out from firebase.')

    // clean user from store
  } catch (error) {
    console.error('signOutUser (firebase/auth.js): ', error)
  }
}

export { signInPopup, signOutUser }

However, you should be careful with this approach, since you will have to log out user from both services at once.

turivishal
  • 34,368
  • 7
  • 36
  • 59
0

Lazar Kulasevic answer was fine before gapi.client.init became deprecated. Now you should use GIS as recommended in https://developers.google.com/identity/gsi/web/guides/overview

Here is a sample of Angular code that use firebase and gapi :

index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
    ...
    </head>
    <body>
        <script src="https://apis.google.com/js/api.js" type="text/javascript" ></script>
        <script src="https://accounts.google.com/gsi/client" async defer></script>
        <app-root></app-root>
        ...
    </body>
</html>

In a auth.service.ts file :

/// <reference path="../../../node_modules/@types/gapi/index.d.ts" />
declare const google: any;

@Injectable({
    providedIn: 'root',
})
export class AuthService {

    login() {
        const client = google.accounts.oauth2.initTokenClient({
            client_id: environment.clientId,
            scope: 'https://www.googleapis.com/auth/calendar',
            callback: (authResponse: { access_token: any }) => {
                // initialize gapi
                gapi.load('client', () => {
                    gapi.client
                        .init({}) // don't provide any param
                        .then(() => {
                            // Load the Calendar API discovery document for exemple 
                            gapi.client
                                .load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest')
                                .then(async () => {
                                    console.log('gapi calendar loaded ');
                                });
                        })
                        .catch((reason) => console.log('gapi.client.init failed', reason));
                });

                // initialize firebase auth
                const credential = GoogleAuthProvider.credential(null,authResponse.access_token);
                signInWithCredential(getAuth(), credential)
                    .then((auth: UserCredential) => {
                        ...
                    })
                    .catch((error) => this.#subject$.error(error));
                }
        });
        client.requestAccessToken();
    }
...

npm install the "@types/gapi" to get gapi reference

I suggest you read https://fireship.io/lessons/google-calendar-api-with-firebase/ and https://developers.google.com/identity/oauth2/web/guides/migration-to-gis

Vincent GODIN
  • 467
  • 4
  • 8