Leon Chaewon Kong's dev blog

Google OAuth using Firebase in React

Firebase offers simple, easy way to implement Authentication including e-mail sign-in and OAuth like Google, Facebook, Apple, GitHub, and so on.

In this post, I will focus on Google OAuth.

Flow

First you need to understand the OAuth flow.

  1. When a user clicks authenticate button, by redirect or popup, the user sigins in with Google and Google responds with user data and accessToken.
  2. Then, the accessToken provided by Google is sent to your server, and your server stores the user data.
  3. After that, your server responds with accessToken(generated by your server).
  4. Finally, the accessToken generated from your server stores in either sessionStorage or localStorage to keep the session.

Since this article is about Google OAuth implementation, further concepts related to authentication will not be part of it.

Requirements

You need to install firebase library.

$ npm i firebase

Then, you need to have api-key and auth-domain.

import firebase from 'firebase';

const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOAMIN,
};
firebase.initializeApp(config);

You can find your api-key and auth-domain in your Firebase console like below.

var firebaseConfig object contains apiKey and authDomain.

You can add these to your .env file.

REACT_APP_FIREBASE_API_KEY=YOUR_FIREBASE_API_KEY
REACT_APP_FIREBASE_AUTH_DOAMIN=YOUR_FIREBASE_AUTH_DOMAIN

Implementation

You can choose either official Google library(react-firebaseui) or make your own auth component.

Here you can find docs and examples about react-firebaseui.

In this post, I will create custom UI and Logic.

import React from 'react';
import * as S from './styled';

export const GoogleAuth = () => {
  // ...

  return (
  <S.Container>
    <S.IconWrapper>
      <Icon name='TEMP_LOGO' size='96' />
    </S.IconWrapper>
    <S.ButtonWrapper>
      <S.Button onClick={signInWithGoogle}>
        <S.ButtonText>Start With Google Account</S.ButtonText>
      </S.Button>
    </S.ButtonWrapper>
  </S.Container>
  );
}

I used styled-components in ./styled.ts to add styles to the component.

Next, let’s create signInWithGoogle onClick handler.

you can choose either ‘popup’ or ‘redirect’ in this case.

  1. ‘popup’ opens new window to handle Google OAuth. The method(signInWithPopup) returns promise that would resolves with auth response.
  2. ‘redirect’ changes current page to google auth page(method: signInWithRedirect). After sign in or sign up, the user is going to redirected to the recent page. You have to call another method(getRedirectResult) to get auth response.

Sign in with popup

Sign in with popup is simple.

import { useHistory } from 'react-rouder-dom';

// ...
const history = useHistory()
const signInWithGoogle = async () => {
  const provider = new firebase.auth.GoogleAuthProvider();
  const { credential } = firebase.auth().signInWithPopup(provider);

  if (credential) {
    try {
      const requestParams = {
        provider: 'google',
        accessToken: credential.accessToken,
      };
      const { data } = await axios
        .post<{accessToken: string}>(`${apiBaseURL}/token`, requestParams);
      
      sessionStorage.setItem('accessToken', data.accessToken);
      history.push('/url-to-redirect');
    } catch (e) {
      // Error hanlding logic
    }
  }
}

The line below is the logic that requests API server(your server)’s accessToken with Google’s accessToken.

const { data } = await axios
  .post<{accessToken: string}>(`${apiBaseURL}/token`, requestParams);

Sign in with redirect

Sign in with redirect is a little bit complicated, since you need to implement onClick redirect handler and redirect result hanlder separately.

Button Click Handler

const signInWithGoogle = async () => {
  const provider = new firebase.auth.GoogleAuthProvider();
  firebase.auth().signInWithRedirect(provider);
};

Result Handler

  import { useHistory } from 'react-rouder-dom';

  // ...
  const history = useHistory();
  const accessToken = sessionstorage.getItem('accessToken');

  useEffect(() => {
    getGoogleOAuthResponse();
  }, []);

  useEffect(() => {
    if (token) {
      // Pretend the token is valid; 
      // You need to implement proper token verification logic here.
      // If token is valid, redirect user to desired route.
      history.push('/url-to-redirect')
    }
  }, [token]);

  const getGoogleOAuthResponse = async () => {
    // "as any" Resolves "Property 'accessToken' does not exist on type 'AuthCredential'" error
    const { credential } = await firebase.auth().getRedirectResult() as any;
    if (credential) {
      try {
        const requestParams = {
          provider: 'google',
          accessToken: credential.accessToken,
        };
        const { data } = await axios
          .post<{token: string}>(`${apiBaseURL}/token`, requestParam);

        sessionStorage.setItem('accessToken', data.token);
        history.push('/rooms');
      } catch(e) {
        // Error hanlding logic
      } 
    }
  };

Example Code

import { apiBaseURL } from 'api/constants';
import axios from 'axios';
import { useOAuth } from 'api/useOAuth';
import { Icon } from 'components';
import firebase from 'firebase';
import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';

import { LOGIN_STATE } from '../../constants';
import * as S from './styled';

// Configure Firebase.
const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOAMIN,
};
firebase.initializeApp(config);


export const GoogleAuth = () => {
  const history = useHistory();
  const { loginState } = useOAuth();

  useEffect(() => {
    getGoogleOAuthResponse();
  }, []);

  useEffect(() => {
    if (loginState === LOGIN_STATE.SUCCESS) {
      history.push('/user');
    }
  }, [loginState]);

  const getGoogleOAuthResponse = async () => {
    const { credential } = await firebase.auth().getRedirectResult() as any;

    if (credential) {
      const requestParam = {
        provider: 'Google',
        access_token: credential.accessToken,
      };
      try {
        const { data } = await axios
          .post<{token: string}>(`${apiBaseURL}/token`, requestParam);
        sessionStorage.setItem('accessToken', data.token);
        history.push('/user');
      } catch(e) {
        console.warn(e);
      }
    }
  };

  const signInWithGoogle = () => {
    const provider = new firebase.auth.GoogleAuthProvider();
    firebase.auth().signInWithRedirect(provider);
  };

  return (
    <S.Container>
      <S.IconWrapper>
        <Icon name='TEMP_LOGO' size='96' />
      </S.IconWrapper>
      <S.ButtonWrapper>
        <S.Button onClick={signInWithGoogle}>
          <S.ButtonText>Start With Google Account</S.ButtonText>
        </S.Button>
      </S.ButtonWrapper>
    </S.Container>
  );
}

Reference