Authentication in React can be a complex feature to put in place. If you are building a complex application you will need a REST API to function as the back-end while your React application acts as the front-end. A bulk of the authentication work will happen server side. How does Firebase fit into this? Firebase provides you with an easy way to authenticate users, along with a host of other features.
The source code for this tutorial is available at Github.
Let’s get started with Firebase console to create a new project.
Enter the name for your project in the pop-up that appears and submit. You’ll need to enable the authentication type that we’ll be making use of, which is Email and Password. Follow the directions shown in the image below.
We will generate our application using create-react-app. If you do not have that installed on your machine already, you can run the command below:
npm install create-react-app -g
Now you can generate the application
create-react-app react-firebase
cd react-firebase/
We’ll be making use of the following packages:
React Router — for routing
Firebase — to connect to our project instance on Firebase console
Rebass — to make the application look better
Let’s install them
yarn add react-router-dom firebase rebass
We need to create a configuration file that will enable the application to connect to our Firebase instance. First, get your config from your Firebase console.
Create a new file in your src directory, import firebase and paste the config from Firebase console. Here is a sample of how the file should look like:
#src/firebase.js
import firebase from 'firebase';
const config = {
apiKey: 'xxxxxx',
authDomain: 'xxxxxx.firebaseapp.com',
databaseURL: 'https://xxxxxx.firebaseio.com',
projectId: 'xxxxxx',
storageBucket: '',
messagingSenderId: 'xxxxxx',
};
firebase.initializeApp(config);
export default firebase;
React and Firebase authentication using Email and Password includes two types of routes: private and public. The Home, Login and Register routes will be public. While the Dashboard route will be private. Let’s create those.
The Home component will be a simple component like this:
import React from 'react';
import { Container, Row, Column, Heading } from 'rebass';
const Home = () => {
return (
<Container>
<Row>
<Column>
<Heading>This is home</Heading>
</Column>
</Row>
</Container>
);
};
export default Home;
In the Home component, we are only rendering a text.
The Register component will contain two input fields for email and password, along with a submit button. The state of the email and password field will be changed as values are typed in. When the button is clicked, we want to trigger a method that submits the form value and creates a new account for the user. Let’s see that in the code.
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { Container, Flex, Box, Input, Button, Subhead, Text } from 'rebass';
import firebase from './firebase';
class Register extends Component {
state = {
email: '',
password: '',
error: null,
};
handleInputChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
const { email, password } = this.state;
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((user) => {
this.props.history.push('/');
})
.catch((error) => {
this.setState({ error: error });
});
};
render() {
const { email, password, error } = this.state;
return (
<Container>
<Flex>
<Box>
<Subhead>Register</Subhead>
</Box>
</Flex>
{error ? (
<Flex>
<Box>
<Text>{error.message}</Text>
</Box>
</Flex>
) : null}
<Flex>
<Box>
<form onSubmit={this.handleSubmit}>
<Input type="text" name="email" placeholder="Email" value={email} onChange={this.handleInputChange} />
<Input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={this.handleInputChange}
/>
<Button children="Register" />
</form>
</Box>
</Flex>
</Container>
);
}
}
export default withRouter(Register);
The initial state of email and password is an empty string. There are possibilities of our users making mistakes which will result in errors. So, we need to handle this — that’s why we have a const variable for this. The handleInputChange() method is called whenever a value is entered in any of the form input fields. It updates the state of either the email and password with the entered value. The handleSubmit() method is called when the user clicks the submit button. We use ES6 destructuring to get the values for email and password. Next, we use the Firebase createUserWithEmailAndPassword()method which is available in auth() to create the user using the email and password we obtained. Upon authentication, the user can then access the dashboard.
The Login component will look like the Register component.
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { Container, Flex, Box, Input, Button, Subhead, Text } from 'rebass';
import firebase from './firebase';
class Login extends Component {
state = {
email: '',
password: '',
error: null,
};
handleInputChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
const { email, password } = this.state;
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((user) => {
this.props.history.push('/');
})
.catch((error) => {
this.setState({ error: error });
});
};
render() {
const { email, password, error } = this.state;
return (
<Container>
<Flex>
<Box>
<Subhead>Log In</Subhead>
</Box>
</Flex>
{error ? (
<Flex>
<Box>
<Text>{error.message}</Text>
</Box>
</Flex>
) : null}
<Flex>
<Box>
<form onSubmit={this.handleSubmit}>
<Input type="text" name="email" placeholder="Email" value={email} onChange={this.handleInputChange} />
<Input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={this.handleInputChange}
/>
<Button children="Log In" />
</form>
</Box>
</Flex>
</Container>
);
}
}
export default withRouter(Login);
The main difference is the use of signInWithEmailAndPassword() method which is provided to us by Firebase.
If you look at the export line of both files you will see that the exported component is wrapped as a value passed to withRouter. We are using it to access the history object. Without it, we will not be able to direct the registered or logged in users.
The Logout feature will be a component that renders a button.
import React from 'react';
import { Button } from 'rebass';
import firebase from 'firebase';
const logOutUser = () => {
firebase.auth().signOut();
};
const LogOut = () => {
return <Button onClick={logOutUser} children="Log Out" />;
};
export default LogOut;
When the button is clicked it will call a method which is wired to call Firebase’s signOut() method.
The /dashboard route has to be accessible to only authenticated users. For that, to work, we need to create a special kind of route that checks whether the user is authenticated or not. First, we need to create an authenticated, which will be passed down the component tree. This state will be created in the App component.
import React, { Component } from 'react';
import Navigation from './Navigation';
import firebase from './firebase';
class App extends Component {
state = {
authenticated: false,
};
componentDidMount() {
firebase.auth().onAuthStateChanged((authenticated) => {
authenticated
? this.setState(() => ({
authenticated: true,
}))
: this.setState(() => ({
authenticated: false,
}));
});
}
render() {
return <Navigation authenticated={this.state.authenticated} />;
}
}
export default App;
componentDidMount() is a lifecycle method that is executed after the first render. In the lifecycle method we have onAuthStateChanged(). This is a Firebase function that receives an anonymous function as an input. The anonymous function has access to authenticated. This function is called whenever the state of authenticated changes.
The state is then passed down to the Navigation component as authenticated props. You will see how we make use of it there.
Let’s create the ProtectedRoute component.
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ProtectedRoute = ({ component: Component, ...rest, authenticated }) => {
return <Route render={(props) => (authenticated ? <Component {...props} /> : <Redirect to="/login" />)} {...rest} />;
};
export default ProtectedRoute;
The ProtectedRoute component makes use of the same API as Route. It renders a Route which will match depending on the value of authenticated since no path was supplied. If the value of authenticated is true, the component which will be passed when ProtectedRoute component is used in the Navigation component will be rendered. Else the user will be redirected to the login page.
The Dashboard component is going to be visible for only authenticated users. For now, we’ll point users there, after they are logged in or registered. It will be as simple as the Home component.
import React from 'react';
import { Container, Flex, Box, Heading } from 'rebass';
const Dashboard = () => {
return (
<Container>
<Flex>
<Box>
<Heading>Welcome!</Heading>
</Box>
</Flex>
</Container>
);
};
export default Dashboard;
Next, let’s create the Navigation
import React, { Component } from 'react';
import { Row, Column } from 'rebass';
import { BrowserRouter as Router, Route, Switch, NavLink } from 'react-router-dom';
import Home from './Home';
import Login from './Login';
import Register from './Register';
import Dashboard from './Dashboard';
import ProtectedRoute from './ProtectedRoute';
import LogOut from './LogOut';
class Navigation extends Component {
render() {
return (
<Router>
<div>
<Row>
<Column>
<NavLink to="/">Home</NavLink>
{this.props.authenticated ? (
<span>
<NavLink to="/dashboard">Dashboard</NavLink>
<LogOut />
</span>
) : (
<span>
<NavLink to="/login">Login</NavLink>
<NavLink to="/register">Register</NavLink>
</span>
)}
</Column>
</Row>
<Switch>
<Route exact path="/" component={Home} />
<Route authenticated={this.props.authenticated} path="/login" component={Login} />
<Route path="/register" component={Register} />
<ProtectedRoute authenticated={this.props.authenticated} path="/dashboard" component={Dashboard} />
</Switch>
</div>
</Router>
);
}
}
export default Navigation;
The links that will be displayed on the navigation depends on the authenticated state of the user — when authenticated is true, the link dashboard and the logout button will be shown, else the register and login links will be shown. When a user who is not authenticated tries to access the /dashboard route through the browser’s address bar, the user will be redirected to /login.
Remember we passed authenticated from the App component to the Navigation component as props. It is the value of this props that we use to determine if a user is authenticated or not. The props are also passed down to the ProtectedRoute component. Now start up your server to see this in action.
Depending on how complex your application is, you can harness the power of Firebase. Firebase also provides you with other sign-in methods such as Facebook, Google, e.t.c. From your Firebase console, you can be able to manage users who are registered on your application.
Leave a Reply
Your email address will not be published. Required fields are marked *