Firebase authentication with react and redux


Firebase authentication is replacing the Google Identity toolkit.  I'm not sure if I really understand the 'firebasification' of many of Google's APIS. I think it's  confusing whether you should use the Firebase version of things, or the underlying Google APIS (an example is Google Cloud storage and messaging versus their newer Firebase equivalents). In any case, Firebase Authentication is really nicely put together, and you can even make it work with Redux and React. Here's how.

The console

Just as with the Google developer console, there's a firebase console. You create an application there and get some credentials. Click on web setup, and Firebase helpfully sets up some credentials you can copy straight into your app



Here you also provide the authorized domains that can access your app


Managing users

One of the nice things you get with Firebase is that it manages your registered users for you. This means that you can forget all about keeping a separate store of registered users, unless you want to keep additional information about them other than their identity. If you do, then Firebase also makes that easy by providing a unique id you can use (in another Firebase database) to keep other user related stuff. In a future post, I'll show how to do that too. You can also use the Google credentials (or Twitter, Github, Facebook etc) for sign-in so you don't need to store passwords or email addresses. In my App, I'm only going to use Google sign-in.



Your dashboard shows your user database.

React and Redux

Let's look at integrating all that into React and Redux. In this example I'm using webpack, and building my app in a Node environment, in which I've installed the firebase library, and the main topic is how to interact with the Redux store, but this should be adaptable if you are building an app directly using script tags without Redux (or React), as the principles are the same. You'll also need the Material-UI module.

The App

The UI contains a sign in element on its Appbar. The rest of this post will look at how to manipulate that.

Which changes to this when someone is signed in.

Signing out happens when the ID chip is closed. 

Getting firebase running

You need this
import firebase from 'firebase';

and firebase auth is initialized like this, where ca.config contains the credentials that were produced by the Firebase console.
const firebaseApp = firebase.initializeApp(ca.config);
const firebaseAuth = firebaseApp.auth();

Signing in

The render method of the UI component that handles the sign in status looks like this. It uses some components from Material-UI. Note that it uses the status from the store (passed in props from a higher level component) to decide what to render, and the user data from the store if it is available.
render() {
    let item;
    const props = this.props;
    
    if ( props.auth.status === cs.status.AUTH_LOGGED_IN ) {
      item = <span><Chip 
        onRequestDelete={this.signout.bind(this)}
      >
       <Avatar
              src={props.auth.photoURL}
              size={40} />
              {props.auth.displayName}
        </Chip>
        </span>;
    }
    else if (props.auth.status === cs.status.AUTH_AWAITING_RESPONSE || props.auth.status === cs.status.AUTH_AWAITING_USER) {
      item = <span></span>;
    }
    else {
      item = 
        <ListItem
          primaryText={<FlatButton style={this.props.itemContainer} onClick={this.signin.bind(this)} label="Sign in" />}
          style={this.props.itemContainer}
        />;
    }

    return (
      <List style={this.props.listContainer}>{item}</List> 
    );

  }

The onClick events dispatch action creators to the Redux store which look like this.
  signin() {
    this.props.dispatch (acSignin());
  } 
  
  signout() {
    this.props.dispatch (acSignout());
  }

Here I'm only using the Google sign-in method, which is asynchronous. For more on dealing with promises in redux action creators, see Creating promise actions in redux
/**
 * sign in to firebase
 */
export function acSignin() {
  const provider = new firebase.auth.GoogleAuthProvider();

  return acPromise (
    cs.actions.AUTH_SIGNIN,'google',
    () => {
      return firebaseAuth.signInWithPopup(provider);
    }
  );
}

And the signout is also asynchronous
/**
 * sign out of firebase
 */
export function acSignout() {

  return acPromise (
    cs.actions.AUTH_SIGNOUT,
    getCurrentUid(), 
    () => firebase.signOut
  ); 

}

The current uid is used for error logging, so it can be retrieved from the store like this.
function getCurrentUid() {
  const state = Process.store.getState();
  return state.auth ? state.auth.uid : "";
}

The reducers

The initial state (when there is nobody signed in) is this
               const initialState = {
                 status:cs.status.AUTH_UNKNOWN,
                 error:null,
                 displayName:'',
                 uid:'',
                  photoURL:'',
               email:''
               };

Since this is asynchronous we have 3 sub actions for each action to handle. Note that a successful sign in gives you user information plus the credentials property. If you need an access token to get to other APIS (you can specify additional scopes when you make an authentication request), then you'll find it in credentials. Although the user information is also available here, firebase recommends that you pick that up by watching for user changes (which I'll get to in a moment).
case cs.actions.AUTH_SIGNOUT+'_PENDING': 
return {...initialState, status: cs.status.AUTH_AWAITING_RESPONSE };
    case cs.actions.AUTH_SIGNOUT+'_REJECTED': 
return {...initialState, status: cs.status.AUTH_UNKNOWN,error:action.payload };
    case cs.actions.AUTH_SIGNOUT+'_FULFILLED': 
return {...initialState};

    case cs.actions.AUTH_SIGNIN+'_PENDING': 
return {...initialState, status: cs.status.AUTH_AWAITING_RESPONSE };
    case cs.actions.AUTH_SIGNIN+'_REJECTED': 
return {...initialState, status: cs.status.AUTH_UNKNOWN,error:action.payload };
    case cs.actions.AUTH_SIGNIN+'_FULFILLED': {
    // its possible that firebase will have fired its auth_user action first
    // so only set to waiting if its not logged in
    if (state.status === cs.status.AUTH_LOGGED_IN) {
    return {...state, credential:action.payload.credential};
   
    else {
return {
...initialState, 
credential:action.payload.credential,
status: cs.status.AUTH_AWAITING_USER
};
    }
    }

Waiting for changes in auth state

When a user signs in or out, firebase fires the onAuthStateChanged event. All you need to do is wait for it to happen. In this example Process.store is my Redux store for my app.
 // this is about signing in and out
  firebaseAuth.onAuthStateChanged(
    user => {
      const state = Process.store.getState();
      if (user && state.auth.uid && state.auth.uid === user.uid) {
        // do nothing as this can get fired on a refresh token, 
        // but the user hasn't actually changed
      }
      else {

        // we've received a new user so dispatch an action to send the new user info to the store.
        Process.store.dispatch(acAuthUser(user));
      }
    }

The action creator

When a change in user is detected, this action is created
/**
 * change in user fired
 */
export function acAuthUser(user) {

  return {
    type: cs.actions.AUTH_USER,
    payload: user
  };

}

The reducer

This will be called in due course to store (or remove) the user info in the redux store
case cs.actions.AUTH_USER:
return action.payload ? {...state, 
status: cs.status.AUTH_LOGGED_IN, 
displayName:action.payload.displayName,
uid:action.payload.uid,
photoURL:action.payload.photoURL,
email:action.payload.email
} : {...initialState};

Summary

For those not yet familiar with Redux, this seems like a lot of code in different places to try to follow, but as your app gets more complex, you'll see that the pattern is entirely repeatable for many functions and leads to a much more robust handling of UI asynchronicity. 





For more like this, see React, redux, redis, material-UI and firebase. Why not join our forum, follow the blog or follow me on twitter to ensure you get updates when they are available.
Comments