React + Firebase — Persistent Auth

David Lazic
3 min readOct 6, 2017

--

Lately I’ve been playing around with Firebase and so far — I like it. It’s got its own quirks, but nothing unbearable. It’s analogous to the MEAN stack, except that with Firebase we have a real time API based on a WebSocket protocol out of the box.

One of the peculiar things of building an app based on a WebSocket approach is the persistent Auth implementation.
Personally, I’m used to working with stateless, REST based APIs which have a ping method that you can call from the app. The app would send http GET request with a bearer token — > if the token is valid we would retrieve currently logged in user or receive invalid token error.

With Firebase, there is no such thing as a ping request. Here we need to have a reactive, event based system implemented by setting an observer on Firebase’s auth.

So, let’s demonstrate a simple view routing system with public / protected routes.

Notice that snippets are based on React Router v3, but the similar approach can be applied to v4.

<Route path={ publicPath } component={ App } >
<IndexRoute component={ Home } />
<Route path={ routeCodes.LOGIN } component={ Login } />
<Route path={ routeCodes.ADMIN } component={ Admin } />
</Route>

We have an App component acting as a wrapper for all the child routes. Initial render would present us a Home component, Login route will serve as our public route and we want to restrict access to Admin route if user isn’t logged in.

When we browse our public (“/login”) route we can present our “smart” Login view which implements onLogin method for a user to sign in to the Firebase.

onLogin ({ email, password }) {
firebase.auth().signInWithEmailAndPassword(email, password))
.then(() => browserHistory.push(routeCodes.ADMIN))

If Firebase responds with a success, we redirect our user to the Admin route — simple enough.

So how do we protect the Admin route?

One approach is to use Routes’ onEnter method and do a check of a current user in Firebase via firebase.auth().currentUser. But here’s the kicker — we can’t use this approach in case of a browser refresh directly on a protected route (“/admin”) as there’s no guarantee that Firebase package will be instantiated at that point, so most of the times currentUser will be null.
This is the question that’s been repeatedly asked throughout the web.

The other approach is to create an AuthResolver wrapper component, which in our case can be the App component itself.

firebase.auth().onAuthStateChanged(user => this.setState({ user }));

When our resolver mounts, we set an observer to Firebase’s auth, and we store the observer’s response inside the state, whatever the user might be — user object or null.

Since setState triggers componentWillUpdate method we can do our protected route check there and immediately do a redirection in case user isn’t logged in. Otherwise, we’ll let React render the requested route.

componentWillUpdate (props, state) {
if (!this.isAuthorized(props.children.props.route.protected, state.user)) {
browserHistory.push(routeCodes.ROOT);
}
}

What’s left is to add protected flag onto each route we want to restrict access to.

<Route path={ routeCodes.ADMIN } component={ Admin } protected />

This approach ensures we have a single entry point for the Auth check which is easy to scale up in a more complicated case of different protected states.
It somewhat reminds me of writing an attribute directive back in Angular 1x which is neat.

Here are the full gists:

--

--