React Firebase Authentication
Tags: react, firebase, authentication, javascript
Published: 2018-06-18 21:56:28

---
title: React Firebase Authentication
published: true
description: React Firebase Authentication using the recent releases v16+ the Context API
tags: react, firebase, authentication, javascript
cover_image: https://thepracticaldev.s3.amazonaws.com/i/rlnfdec6a5apzrty2ag3.png
---

We're going to build a simple authentication and secure application in React and Firebase Authentication SDKs. Users will have the ability to **create account**, **sign in** and **sign out**. We'll also make certain routes (private pages) secure and protected to be only used by authenticated users. I hope you find it helpful!

### Application Installation & Setup

To get started we'll create an application that will be bootstrapped with Facebook’s official React boilerplate [create-react-app](https://github.com/facebook/create-react-app).

> Run `npm i -g create-react-app` to have it installed on your local machine

```bash
# Creating an App
  create-react-app react-firebase-auth
# Change directory
  cd react-firebase-auth
# Additional packages to install
  yarn add firebase react-router-dom react-props
```

The initial project structure is now generated, and all dependencies installed successfully. Let's rock up our project hierarchy and it's folder structure as showing below:

```bash
# Make sub-directories under src/ path
  mkdir -p src/{components,firebase,shared}
# Move App component under src/components
  mv src/App.js src/components
# Create desired files to work with
  touch src/components/{AppProvider,Navbar,FlashMessage,Login,Signup}.js
  touch src/firebase/{firebase,index,auth,config}.js
  touch src/shared/Form.js
```

We need to make sure that everything fall into place by listing all created files, and sub-directories via command line `cd src/ && ls * -r`

```bash
# terminal

react-firebase-auth % cd src/ && ls * -r
components:
App.js   AppProvider.js  FlashMessage.js Login.js  Navbar.js  Signup.js

firebase:
auth.js  config.js   firebase.js index.js

shared:
Form.js
```

## Firebase

We're not going to deep dive into Firebase itself. 
If you're not familiar with Firebase please make sure you check their guide 
on how to [Add Firebase to your JavaScript Project](https://firebase.google.com/docs/web/setup)

### Firebase configuration

```javascript
// src/firebase/config.js

const devConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "AUTH_DOMAIN",
  databaseURL: "DATABASE_URL",
  projectId: "PROJECT_ID",
  storageBucket: "STORAGE_BUCKET",
  messagingSenderId: "MESSAGING_SENDER_ID"
};

const prodConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "AUTH_DOMAIN",
  databaseURL: "DATABASE_URL",
  projectId: "PROJECT_ID",
  storageBucket: "STORAGE_BUCKET",
  messagingSenderId: "MESSAGING_SENDER_ID"
};

export {
  devConfig,
  prodConfig
}
```

#### Config breakdown

- **devConfig** used for development environment
- **prodConfig** used for production environment

📌 its alway good to have a config template file for your project with predefined setup (*as showing above*) to avoid pushing sensitive data to a repository. You or any one of your team can later make a copy of this template with the proper file extension. Example (*based on this post*): Create a file `firebase.config` open your `.gitignore` and **add** `app/config.js` then run `cp app/firebase.config app/config.js` to copy of that config template.


### Firebase initialization

```javascript
// src/firebase/firebase.js

import * as firebase from 'firebase';
import { devConfig } from './config';

!firebase.apps.length && firebase.initializeApp(devConfig);

const auth = firebase.auth();

export {
  auth
}
```

### Auth module

```javascript
// src/firebase/auth.js

import { auth } from './firebase';

/**
 * Create user session
 * @param {string} action - createUser, signIn
 * @param {string} email 
 * @param {string} password 
 */
const userSession = (action, email, password) => auth[`${action}WithEmailAndPassword`](email, password);

/**
 * Destroy current user session
 */
const logout = () => auth.signOut();

export {
  userSession,
  logout
}
```

#### Auth module breakdown

- **userSession** a function that accepts three params action: decides whether user creates an account or login, email and password
- **logout** destroys the current user session and log the user out of the system

### Firebase module

```javascript
// src/firebase/index.js

import * as auth from './auth';
import * as firebase from './firebase';

export {
  auth,
  firebase
}
```

## Components

### Provider component

```javascript
// src/components/AppProvider.js

import React, {
  Component,
  createContext
} from 'react';
import { firebase } from '../firebase';
export const {
  Provider,
  Consumer
} = createContext();

class AppProvider extends Component {
  state = {
    currentUser: AppProvider.defaultProps.currentUser,
    message: AppProvider.defaultProps.message
  }

  componentDidMount() {
    firebase.auth.onAuthStateChanged(user => user && this.setState({
      currentUser: user
    }))
  }

  render() {
    return (
      <Provider value={{
        state: this.state,
        destroySession: () => this.setState({ 
          currentUser: AppProvider.defaultProps.currentUser 
        }),
        setMessage: message => this.setState({ message }),
        clearMessage: () => this.setState({ 
          message: AppProvider.defaultProps.message 
        })
      }}>
        {this.props.children}
      </Provider>
    )
  }
}

AppProvider.defaultProps = {
  currentUser: null,
  message: null
}

export default AppProvider;
```

#### AppProvider breakdown

AppProvider is a React component provides a way to pass data through the component tree without having to pass props down manually at every level and allows **Consumers** to subscribe to *context changes*. 

- **componentDidMount** after a component is mounted, we check against user 
existence.
 
### Navbar component

```javascript
// src/components/Navbar.js

import React from 'react';
import {
  Link,
  withRouter
} from 'react-router-dom';
import { auth } from '../firebase';
import { Consumer } from './AppProvider';

const Navbar = props => {
  const handleLogout = context => {
    auth.logout();
    context.destroySession();
    props.history.push('/signedOut');
  };

  return <Consumer>
    {({ state, ...context }) => (
      state.currentUser ?
        <ul>
          <li><Link to="/dashboard">Dashboard</Link></li>
          <li><a onClick={() => handleLogout(context)}>Logout</a></li>
        </ul>
        :
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/login">Login</Link></li>
          <li><Link to="/signup">Create Account</Link></li>
        </ul>
    )}
  </Consumer>
};

export default withRouter(Navbar);
```

#### Navbar breakdown

The Navbar component handles UI logic as the following:

1. If the system logged in user then we show Dashboard (protected page) and the Logout button which kicks out the user and redirect to `/signedOut` page.
2. If no users found then we display Home, Login and Create and Account links.

### FlashMessage component

```javascript
// src/components/FlashMessage.js

import React from 'react';
import { Consumer } from '../components/AppProvider';

const FlashMessage = () => <Consumer>
  {({ state, ...context }) => state.message && <small className="flash-message">
    {state.message}
    <button type="button" onClick={() => context.clearMessage()}>Ok</button>
  </small>}
</Consumer>;

export default FlashMessage;
```

#### FlashMessage breakdown

FlashMessage is a stateless component wrapped by **Consumer** that subscribes to context changes. It shows up when something goes wrong (i.e. Form validation, server error, etc...). The FlashMessage has "Ok" button that clears it up and close/hide it.

### <a name="shared-form"></a> Form component

```javascript
// src/shared/Form.js

import React, {
  Component,
  createRef
} from 'react';
import PropTypes from 'prop-types';
import { auth } from '../firebase';

class Form extends Component {
  constructor(props) {
    super(props);

    this.email = createRef();
    this.password = createRef();
    this.handleSuccess = this.handleSuccess.bind(this);
    this.handleErrors = this.handleErrors.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSuccess() {
    this.resetForm();
    this.props.onSuccess && this.props.onSuccess();
  }

  handleErrors(reason) {
    this.props.onError && this.props.onError(reason);
  }

  handleSubmit(event) {
    event.preventDefault();
    const {
      email,
      password,
      props: { action }
    } = this;

    auth.userSession(
      action,
      email.current.value,
      password.current.value
    ).then(this.handleSuccess).catch(this.handleErrors);
  }

  resetForm() {
    if (!this.email.current || !this.password.current) { return }
    const { email, password } = Form.defaultProps;
    this.email.current.value = email;
    this.password.current.value = password;
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <h1>{this.props.title}</h1>
        <input
          name="name"
          type="email"
          ref={this.email}
        />
        <input
          name="password"
          type="password"
          autoComplete="none"
          ref={this.password}
        />
        <button type="submit">Submit</button>
      </form>
    )
  }
}

Form.propTypes = {
  title: PropTypes.string.isRequired,
  action: PropTypes.string.isRequired,
  onSuccess: PropTypes.func,
  onError: PropTypes.func
}

Form.defaultProps = {
  errors: '',
  email: '',
  password: ''
}

export default Form;
```

#### Form breakdown

- Both **email, password** creates a ref `createRef()` that we attach later to React elements via the ref attribute.
- **handleSuccess** method executes `resetForm` method, and callback function from the giving props (if found any!).
- **handleErrors** method executes the callback function from the giving props (if found any!) with *reason*.
- **handleSubmit** method prevent the default `form` behavior, and executes the `auth.userSession` to create and account or login a user.

### Login component

```javascript
// src/components/Login.js

import React from 'react';
import { withRouter } from 'react-router-dom';
import Form from '../shared/Form';
import { Consumer } from './AppProvider';

const Login = props => <Consumer>
  {({ state, ...context }) => (
    <Form
      action="signIn"
      title="Login"
      onSuccess={() => props.history.push('/dashboard')}
      onError={({ message }) => context.setMessage(`Login failed: ${message}`)}
    />
  )}
</Consumer>;

export default withRouter(Login);
```

#### Login breakdown

Login is a stateless component wrapped by **Consumer** that subscribes to context changes. If successfully logged in the user will be redirect to a protected page (dashboard) otherwise error message will be popped up.

### Signup component

```javascript
// src/components/Signup.js

import React from 'react';
import { withRouter } from 'react-router-dom';
import Form from '../shared/Form';
import { auth } from '../firebase';
import { Consumer } from './AppProvider';

const Signup = props => <Consumer>
  {({ state, ...context }) => (
    <Form
      action="createUser"
      title="Create account"
      onSuccess={() => auth.logout().then(() => {
        context.destroySession();
        context.clearMessage();
        props.history.push('/accountCreated');
      })}
      onError={({ message }) => context.setMessage(`Error occured: ${message}`)}
    />
  )}
</Consumer>;

export default withRouter(Signup);
```

#### Signup breakdown

Signup is a stateless component wrapped by **Consumer** that subscribes to context changes. Firebase by default automatically logs the user in once account created successfully. I've changed this implementation by making the user log in manually after account creation. Once `onSuccess` callback fires we log the user out, and redirect to `/accountCreated` page with custom message and a call to action "Proceed to Dashboard" link to login. If account creation fails error message will be popped up.

### App component (container)

```javascript
// src/components/App.js

import React, {
  Component,
  Fragment
} from 'react';
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom';

import AppProvider, {
  Consumer
} from './AppProvider';
import Login from './Login';
import Signup from './Signup';

import Navbar from '../shared/Navbar';
import FlashMessage from '../shared/FlashMessage';

class App extends Component {
  render() {
    return (
      <AppProvider>
        <Router>
          <Fragment>
            <Navbar />
            <FlashMessage />
            <Route exact path="/" component={() => 
              <h1 className="content">Welcome, Home!</h1>} />
            <Route exact path="/login" component={() => <Login />} />
            <Route exact path="/signup" component={() => <Signup />} />
            <Router exact path="/dashboard" component={() => <Consumer>
              {
                ({ state }) => state.currentUser ?
                  <h1 className="content">Protected dashboard!</h1> :
                  <div className="content">
                    <h1>Access denied.</h1>
                    <p>You are not authorized to access this page.</p>
                  </div>
              }
            </Consumer>} />
            <Route exact path="/signedOut" component={() => 
              <h1 className="content">You're now signed out.</h1>} />
            <Route exact path="/accountCreated" component={() => 
              <h1 className="content">Account created. <Link to="/login">
              Proceed to Dashboard</Link></h1>} />
          </Fragment>
        </Router>
      </AppProvider>
    );
  }
}

export default App;
```

#### App (container) breakdown


Its pretty straightforward right here! The navigational components Routers wrapped by AppProvider to  pass data through the component tree. The `/dashboard` route component has a protected content (page) that is served only for authenticated users, and no users are signed in we display the **Access denied** message instead of our private content/page.


## Demo

Check out [demo-gif here](https://goo.gl/rNV9bS)

---

**Feedback are welcome** If you have any suggestions or corrections to make, please do not hesitate to drop me a note/comment.