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
1 |
import firebase from 'firebase'; |
1 2 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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> ); } |
1 2 3 4 5 6 7 |
signin() { this.props.dispatch (acSignin()); } signout() { this.props.dispatch (acSignout()); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * sign in to firebase */ export function acSignin() { const provider = new firebase.auth.GoogleAuthProvider(); return acPromise ( cs.actions.AUTH_SIGNIN,'google', () => { return firebaseAuth.signInWithPopup(provider); } ); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * sign out of firebase */ export function acSignout() { return acPromise ( cs.actions.AUTH_SIGNOUT, getCurrentUid(), () => firebase.signOut ); } |
1 2 3 4 |
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
1 2 3 4 5 6 7 8 |
const initialState = { status:cs.status.AUTH_UNKNOWN, error:null, displayName:'', uid:'', photoURL:'', email:'' }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 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
1 2 3 4 5 6 7 8 9 10 11 |
/** * 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
1 2 3 4 5 6 7 8 |
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.