React Redux Firebase Authentication – Google Account Sign in/Sign out example

React Redux Firebase Authentication – Google Account Sign in/Sign out example

In this tutorial, we’re gonna add Firebase Authentication to a React Redux Application with Google Account Sign in/ Sign out.

Related Posts:
How to use Firebase Database CRUD Operations in React Webpack
React Redux – Firebase CRUD Operations example

Firebase Authentication

Overview

Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app. It supports authentication using passwords, phone numbers, identity providers like Google, Facebook, Twitter…

Google Account Sign in/Sign out

The easiest way to authenticate users with Firebase using Google Accounts is using Firebase JavaScript SDK. To do this, follow these steps:

Enable Google Sign-In in Firebase console

– Go to Firebase console, open the Auth section.
– On the SIGN-IN METHOD tab, enable the Google and click Save:

react-redux-firebase-auth-example-enable-google-sign-in

Create an instance of the Google provider object


firebase.initializeApp(config);
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();

Authenticate with Firebase


firebase.auth().signInWithPopup(googleAuthProvider).then(result => {
    // Google Access Token.
    const token = result.credential.accessToken;
    console.log(token);
    // user info.
    const user = result.user;
    console.log(user);
    // ...
}).catch(error => {
    const errorCode = error.code;
    const errorMessage = error.message;
    const email = error.email;
    // Firebase Auth Credential type
    const credential = error.credential;
    // ...
});

Get currently signed-in user

– Recommended way is setting an observer on Auth object:


firebase.auth().onAuthStateChanged(user => {
    if (user) {
        const uid = user.uid;
        const name = user.displayName;
        const email = user.email;
        // ...
    } else {
        // no user...
    }
});

– We can also use the currentUser property:


const user = firebase.auth().currentUser;

if (user) {
    const uid = user.uid;
    const name = user.displayName;
    const email = user.email;
    // ...
} else {
    // no user...
}

Sign out


firebase.auth().signOut().then(result => {
    // successful...
}).catch(error => {
    // ...
});

Example Overview

Goal

We will add Google Account Sign in/Sign out to our App:
– When there is no user logging in: only shows Login page.
If we enter any url, app also directs us to this Login page.
Clicking on Login button will popup a window for Google Account.

react-redux-firebase-auth-example-result-click-sign-in

– When a user is logged in, he can access all the pages, and there is a Logout button in the header for signing out.

react-redux-firebase-auth-example-result-logged-in

Solution

– We want a group of urls that can only be accessed by authenticated user, and a group of urls for public.
=> Router with Switch for PrivateRoute and PublicRoute Component.
– We need navigate url to another url in a certain condition.
=> use history from history/createBrowserHistory.

// routers/AppRouter.js

    
        
        
        
        
        
        
    

PublicRoute & PrivateRoute have to check an isAuthenticated boolean variable to know what to do. isAuthenticated bases on a global state to choose its own value: state.auth.uid.
=> create a Reducer for authentication that return uid for state.auth when logged-in and empty object when logged-out:


// routers/PublicRoute.js & routers/PrivateRoute.js
// ...use isAuthenticated
const mapStateToProps = (state) => ({
    isAuthenticated: !!state.auth.uid
});
export default connect(mapStateToProps)(...);

// reducers/auth.js
export default (state = {}, action) => {
    switch (action.type) {
        case 'LOGIN':
            return {
                uid: action.uid
            };

        case 'LOGOUT':
            return {};

        default:
            return state;
    }
};

– combine new reducer with the others:


export default () => {
    return createStore(
        combineReducers({
            books: booksReducer,
            filters: filtersReducer,
            auth: authReducer
        }),
        applyMiddleware(thunk));
};

const demoState = {
    books: [...],
    filters: {...},
    auth: {
        uid : '123uid'
    }
};

– We need actions for auth reducer, and we also need Firebase actions that will be dispatched:


// actions/auth.js
export const login = (uid) => ({
    type: 'LOGIN',
    uid
});

export const logout = () => ({
    type: 'LOGOUT'
});

export const firebaseLogin = () => {
    return () => {
       return firebase.auth().signInWithPopup(googleAuthProvider);
    };
};

export const firebaseLogout = () => {
    return () => {
        return firebase.auth().signOut();
    }
};

– To dispatch Firebase actions in a React Component, we use react-redux connect() method with mapDispatchToProps function:


// components/Login.js
// ...use login()
const mapDispatchToProps = (dispatch) => ({
    login: () => dispatch(firebaseLogin())
});

export default connect(undefined, mapDispatchToProps)(Login);

// components/Header.js
// ...use logout()
const mapDispatchToProps = (dispatch) => ({
    logout: () => dispatch(firebaseLogout())
});

export default connect(undefined, mapDispatchToProps)(Header);

– To dispatch auth actions that affect state.auth.uid (login & logout), we listen to Auth State with onAuthStateChanged:


// app.js
firebase.auth().onAuthStateChanged(user => {
    if (user) {
        store.dispatch(login(user.uid));
        // ...
    } else {
        store.dispatch(logout());
        // ...
    }
});

Technologies

– React 16.3.0
– Redux 3.7.2
– React Redux 5.0.7
– Webpack 4.4.1
– Firebase 4.13.1

Project Structure

react-redux-firebase-auth-example-structure

Practice

Setup React Application with Firebase

Please visit How to add Firebase to React App.

react-redux-firebase-add-firebase-webapp-config

Config Firebase Database Rules

– Go to Firebase console, open the Database section -> Realtime Database.
– On the RULES tab, change .read and .write values. This will allow full read and write access to authenticated users:

react-redux-firebase-auth-example-firebase-database-config-rules

Enable Firebase Auth

– Open the Authentication section.
– On the SIGN-IN METHOD tab, enable the Google and click Save:

react-redux-firebase-auth-example-enable-google-sign-in

Initialize Firebase for Google Account Authentication

firebase/firebase.js


import * as firebase from 'firebase';

const config = {
    apiKey: "xxx",
    authDomain: "jsa-react-redux-firebase.firebaseapp.com",
    databaseURL: "https://jsa-react-redux-firebase.firebaseio.com",
    projectId: "jsa-react-redux-firebase",
    storageBucket: "jsa-react-redux-firebase.appspot.com",
    messagingSenderId: "xxx"
};

firebase.initializeApp(config);
const database = firebase.database();
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();

export { firebase, googleAuthProvider, database as default };

Auth Redux Action

actions/auth.js


import { firebase, googleAuthProvider } from '../firebase/firebase';

export const login = (uid) => ({
    type: 'LOGIN',
    uid
});

export const logout = () => ({
    type: 'LOGOUT'
});

export const firebaseLogin = () => {
    return () => {
       return firebase.auth().signInWithPopup(googleAuthProvider);
    };
};

export const firebaseLogout = () => {
    return () => {
        return firebase.auth().signOut();
    }
};

Auth Redux Reducer

reducers/auth.js


export default (state = {}, action) => {
    switch (action.type) {
        case 'LOGIN':
            return {
                uid: action.uid
            };

        case 'LOGOUT':
            return {};

        default:
            return state;
    }
};

Add Auth to Redux Reducer

store/store.js


import { createStore, combineReducers, applyMiddleware } from "redux";
import booksReducer from '../reducers/books';
import filtersReducer from '../reducers/filters';
import authReducer from '../reducers/auth';
import thunk from 'redux-thunk';

const demoState = {
    books: [
        {
            id: '123abcdefghiklmn',
            title: 'Origin',
            description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.',
            author: 'Dan Brown',
            published: 2017
        }
    ],
    filters: {
        text: 'ori',
        sortBy: 'published', // published or title
        startYear: undefined,
        endYear: undefined
    },
    auth: {
        uid : '123uid'
    }
};

export default () => {
    return createStore(
        combineReducers({
            books: booksReducer,
            filters: filtersReducer,
            auth: authReducer
        }),
        applyMiddleware(thunk));
};

Login Component Page

components/Login.js

import React from 'react';
import { connect } from 'react-redux';
import { firebaseLogin } from '../actions/auth';

const Login = ({ login }) => (
    <div className='container'>
        <button onClick={login}>Login with Google Account</button>
    </div>
);

const mapDispatchToProps = (dispatch) => ({
    login: () => dispatch(firebaseLogin())
});

export default connect(undefined, mapDispatchToProps)(Login);

Logout in Header Component

components/Header.js

import React from 'react';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
import { firebaseLogout } from '../actions/auth';

const Header = ({logout}) => (
    <header>
        <h2>Java Sample Approach</h2>
        <h4>Book Mangement Application</h4>
        <div className='header__nav'>
            <NavLink to='/dashboard' activeClassName='activeNav'>Dashboard</NavLink>
            <NavLink to='/add' activeClassName='activeNav'>Add Book</NavLink>
            <NavLink to='/help' activeClassName='activeNav'>Help</NavLink>
            <button onClick={logout}>Logout</button>
        </div>
    </header>
);

const mapDispatchToProps = (dispatch) => ({
    logout: () => dispatch(firebaseLogout())
});

export default connect(undefined, mapDispatchToProps)(Header);

Control Route

routers/AppRouter.js

import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import DashBoard from '../components/DashBoard';
import AddBook from '../components/AddBook';
import EditBook from '../components/EditBook';
import Help from '../components/Help';
import NotFound from '../components/NotFound';

import createHistory from 'history/createBrowserHistory';
import PublicRoute from './PublicRoute';
import PrivateRoute from './PrivateRoute';
import Login from '../components/Login';

export const history = createHistory();

const AppRouter = () => (
    <Router history={history}>
        <div className='container'>
            <Switch>
                <PublicRoute path="/" component={Login} exact={true} />
                <PrivateRoute path="/dashboard" component={DashBoard} />
                <PrivateRoute path="/add" component={AddBook} />
                <PrivateRoute path="/book/:id" component={EditBook} />
                <PrivateRoute path="/help" component={Help} />
                <Route component={NotFound} />
            </Switch>
        </div>
    </Router>
);

export default AppRouter;

Public/Private Route in Router

routers/PublicRoute.js

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';

const PublicRoute = ({
    isAuthenticated,
    component: Component,
    ...otherProps
}) => (
        <Route {...otherProps} component={(props) => {
            if (isAuthenticated) {
                return (
                    <Redirect to='/dashboard' />
                );
            } else {
                return (
                    <Component {...props} />
                );
            }
        }} />
    );

const mapStateToProps = (state) => ({
    isAuthenticated: !!state.auth.uid
});

export default connect(mapStateToProps)(PublicRoute);

routers/PrivateRoute.js

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import Header from '../components/Header';
import { connect } from 'react-redux';

const PrivateRoute = ({
    isAuthenticated,
    component: Component,
    ...otherProps
}) => (
        <Route {...otherProps} component={(props) => {
            if (isAuthenticated) {
                return (
                    <div>
                        <Header />
                        <Component {...props} />
                    </div>
                );
            } else {
                return (
                    <Redirect to='/' />
                );
            }
        }} />
    );

const mapStateToProps = (state) => ({
    isAuthenticated: !!state.auth.uid
});

export default connect(mapStateToProps)(PrivateRoute);

Listen to Auth State and Render App

app.js

import React from 'react';
import ReactDOM from 'react-dom';
import getAppStore from './store/store';
import { getBooks } from './actions/books';
import './styles/styles.scss';
import { Provider } from 'react-redux';

import AppRouter, { history } from './routers/AppRouter';
import { firebase } from './firebase/firebase';
import { login, logout } from './actions/auth';

const store = getAppStore();

const template = (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

let isRendered = false;
const renderApp = () => {
    if (!isRendered) {
        ReactDOM.render(template, document.getElementById('app'));
        isRendered = true;
    }
}

firebase.auth().onAuthStateChanged(user => {
    if (user) {
        console.log('login user id: ', user.uid);
        console.log('name: ', user.displayName);
        store.dispatch(login(user.uid));
        store.dispatch(getBooks()).then(() => {
            renderApp();
            if (history.location.pathname === '/') {
                history.push('/dashboard');
            }
        });
    } else {
        console.log('logout');
        store.dispatch(logout());
        renderApp();
        history.push('/');
    }
});

Source Code

ReactReduxFirebaseAuth

For running:
yarn install
yarn run dev-server or yarn run build, then yarn run serve.



By grokonez | April 30, 2018.

Last updated on March 31, 2021.



Related Posts


3 thoughts on “React Redux Firebase Authentication – Google Account Sign in/Sign out example”

  1. Thanks for the great post!

    In the reducers/auth.js you return the following:

    return {
       uid: action.uid
    };
    

    Shouldn’t you use Object.assign() to always clone the current state?

    1. Hi Miguel Stevens,

      We should use Object.assign(), or we can also enable the object spread operator proposal to write { ...state, ...newState } instead.

      But, in this case, why do we return the object?
      => Because there is only one field of the state for the reducer: uid. Cloning other fields of the current state doesn’t make sense 🙂

      Best Regards,
      JSA.

  2. Thanks in support of sharing such a nice thinking, paragraph is fastidious, thats why i have read it completely

Got Something To Say:

Your email address will not be published. Required fields are marked *

*