In this tutorial we’ll be building a simple react application with authorization using Firebase with private routes. Before we dig in to the details, i would like to mention about some ready to use best react dashboard templates by WrapPixel and web application templates by AdminMart, which comes with Firebase Authentication.
You can check them out and use directly in your project to save lots of time and money, also they are built by experienced developers, so you get chance to learn a lots as well.
Let’s now move to our tutorial.
Contents
- Introduction
- Prerequisites
- Setting up React
- Setting up Firebase in React
- React Router
- Authentication Context using Firebase and React
- Private Routes in React
- Create View Components in React
- Signup in Firebase
- Sign in Firebase
- SignOut in Firebase
- Conclusion
Introduction
We use authentication to recognizing a user’s identity. At the end of this article, we’ll have built a simple React application that allows users to authenticate using Firebase and we’ll make sure that only authenticated users can access the application.
Prerequisites
- Have basic knowledge of Javascript
- Make sure you have Node>=8.10 and npm>=5.6 on your machine for better experience
- Have basic understanding of React, Context, Hooks, Routes.
- Comfortable with command line
- Text editor
- A Firebase account
Setting up React
To create a project in React run the command bellow:
npx create-react-app react_firebase_auth
npx is a package runner tool that comes with npm 5.2+. We just created a project named react_firebase_auth. Go to the project and start it.
cd react_firebase_auth
npm start
We will need some dependecies for our application such as:
-
react-dom that contains the DOM bindings for React Router, I mean, the router components for websites.
npm install --save react-router-dom
-
material-ui to implement Google’s Material Design.
npm i @material-ui/core --save
Our react application was setuped successfully. Now it’s time to setup the firebase.
Setting up Firebase in React
Let’s begin by creating a new firebase application. To this follow the steps bellow.
Go to Firebase console.
- Press Add Project
- Enter your app name
- Accept the terms
- Press Create Project
- Wait untill the ap will be created
- And press Continue
- Go to Authentication tap
- Click Set up sign-in methods
- Chosse email/password and enable it.
- After it is done go to Project Settings and scroll down to the list of platforms. Select Web.
- Copy your app configuration
On side bar menu click Authentication, go to Sign-in method and enable email/password.
Now let’s setup firebase in our react application. Create firebase.js file in your src folder.
Install firebase dependecie using the command bellow:
npm install --save firebase
Open firebase.js and paste the javascript script that you copied in the Firebase Console.
As you can see, we can iniliaze our firebase app using firebase.initializeApp and exported it as app. Doing so, we have total access to our database.
React Router
Now, go to your App.js and add some routing.
import React from 'react';
import "./App.css";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './Home'
import Login from './Login'
import SignUp from './Signup'
function App() {
return (
<Router>
<div>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={SignUp} />
</div>
</Router>
);
}
export default App;
We wrapped our layout into BrowserRouter that provides Browser Context to all our application. Basically it allows us to use Routes, Links, Redirects and other routers features.
Now, to work with authentication we will need to have to store our authentication states if you’re logged in or not and update our component tree. To do this we’ll use React Context API.
Authentication Context using Firebase and React
Create Authentication.js in your scr folder and past this:
import React, { useEffect, useState } from "react";
import app from "./firebase.js";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [pending, setPending] = useState(true);
useEffect(() => {
app.auth().onAuthStateChanged((user) => {
setCurrentUser(user)
setPending(false)
});
}, []);
if(pending){
return <>Please wait...</>
}
return (
<AuthContext.Provider
value={{
currentUser
}}
>
{children}
</AuthContext.Provider>
);
};
In this file we had to import app from firebase.js where we have our firebase API and we created a context. Context in react is everthing that allows you to propagate some data to the whole react component tree.
And we created a provider component that let’s you store our authentication state. It holders an user and we will update evertime our authentication states will change in firebase. For this we use hook, useEffect, to signup for changes to our firebase object and we pass an empty array to our useEffect as a second argument so that it will run once when component AuthProvider will be mounted in our tree.
Then in our AuthProvider layout we used AuthProvider.Provider and we passed our current user that we get from firebase on every auth state change, we passed it as a value to our AuthProvider.Provider and then we render children passed to this component.
Now, go back to App.js and wrap our layout into AuthProvider.
<AuthProvider>
<Router>
<div>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={SignUp} />
</div>
</Router>
</AuthProvider>
So, everthing bellow it in our component tree will have access to current user through the context API. In our case if you’re logged in you’ll have user object having all the user description and if you´re logged out you’ll have null or undefined state user object.
Private Routes in React
We can create our privates routes to allow only the authenticated users can go to the home page.
Create PrivateRoute.js in your scr folder.
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "./Authentication";
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/login"} />
)
}
/>
);
};
export default PrivateRoute
Here we need to know what component should be render if user is authenticated. So we take component and the rest of the props { component: RouteComponent, ...rest }.
PrivateRouter wil be basically a wrapper on regular routes. So we pass the rest of the props {...rest} and then in our route render function, depending if we have user or not, we will render our route component or redirect to login page.
Go back to your App.js and make these changes:
<AuthProvider>
<Router>
<div>
<PrivateRoute exact path="/" component={Home} />
<Route exact path="/signin" component={Signin} />
<Route exact path="/signup" component={SignUp} />
</div>
</Router>
</AuthProvider>
Now let’s create our view components. We’ll use material UI for this. To get more deeper about Material-UI you can click here to get it done, using the official documentation.
Create View Components in React
We’ll use materil-ui for our interfaces. Make sure you have installed the material-ui dependecie.
NOTE: In this article we’re covering the Firebase Authentication. So, for more details about material-ui go to official documentation.
Create SignIn.js in your src folder and past the code bellow.
import React from 'react';
import Button from '@material-ui/core/Button';
import CssBaseline from '@material-ui/core/CssBaseline';
import TextField from '@material-ui/core/TextField';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Grid from '@material-ui/core/Grid';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import { Link } from 'react-router-dom'
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://pacoconsulting.co.mz/">
PACO IT Consulting
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
export default function SignIn() {
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form onSubmit={handleLogin} className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={8}>
<Copyright />
</Box>
</Container>
);
}
Create SignUp.js in your scr folder.
import React from 'react';
import Button from '@material-ui/core/Button';
import CssBaseline from '@material-ui/core/CssBaseline';
import TextField from '@material-ui/core/TextField';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Grid from '@material-ui/core/Grid';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import { Link } from 'react-router-dom'
function Copyright() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{'Copyright © '}
<Link color="inherit" href="https://material-ui.com/">
Your Website
</Link>{' '}
{new Date().getFullYear()}
{'.'}
</Typography>
);
}
const useStyles = makeStyles((theme) => ({
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(3),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
export default function SignUp() {
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Sign up
</Typography>
<form onSubmit={handleSignUp} className={classes.form} noValidate>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
autoComplete="fname"
name="firstName"
variant="outlined"
required
fullWidth
id="firstName"
label="First Name"
autoFocus
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
variant="outlined"
required
fullWidth
id="lastName"
label="Last Name"
name="lastName"
autoComplete="lname"
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
/>
</Grid>
<Grid item xs={12}>
<TextField
variant="outlined"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
control={<Checkbox value="allowExtraEmails" color="primary" />}
label="I want to receive inspiration, marketing promotions and updates via email."
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign Up
</Button>
<Grid container justify="flex-end">
<Grid item>
<Link to="/signin" variant="body2">
Already have an account? Sign in
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={5}>
<Copyright />
</Box>
</Container>
);
}
Create Home.js in your scr folder.
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
export default function ButtonAppBar() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
News
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</div>
);
}
Signup in Firebase
In SignUp.js file make these changes:
import React, { useCallback} from "react";
import { Link } from 'react-router-dom'
import app from "./firebase.js";
And
export default function SignUp({ history }) {
const classes = useStyles();
const handleSignUp = useCallback(async event => {
event.preventDefault();
const { email, password } = event.target.elements;
try {
await app
.auth()
.createUserWithEmailAndPassword(email.value, password.value);
history.push("/");
} catch (error) {
alert(error);
}
}, [history]);
...
...
...
This component is getting history object from our routing context. When we click on that button our handleSignUp will fire. Inside this function we get our event and calll preventDefault() because we want to reload the page when user clicks on signup button. Then we get out email and password inputs from target.elements and we call createUserWithEmailAndPassword() from firebase API and we pass our email and password values: createUserWithEmailAndPassword(email.value, password.value).
And then we pass our handleSignUp function to onSubmit callback of our form.
Sponsored:
Sign in Firebase
In the SignIn.js file, SignIn.js make these imports:
import React, { useCallback, useContext } from 'react'; // add {useCallback, useContext}
import { withRouter, Redirect } from "react-router";
import app from "./firebase.js";
import { AuthContext } from "./Authentication.js";
In the SignIn() function make these changes:
- Add history
- Add handleLogin method. export default function SignIn({history}) { const classes = useStyles(); const handleLogin = useCallback( async event => { event.preventDefault(); const { email, password } = event.target.elements; try { await app .auth() .signInWithEmailAndPassword(email.value, password.value); history.push("/"); } catch (error) { alert(error); } }, [history] ); const { currentUser } = useContext(AuthContext); if (currentUser) { return ; } ... ... ... ...
There are two differences with SignUp page. Here we use signInWithEmailAndPassword() and we use our authentication contextconst { currentUser } = useContext(AuthContext);
. As you migth have remember we are tracking firebase user and we update our context with currentUser field using our auth model. And then we check: if we have currentUser we render Redirect component from react router. This component when rendered will just redirect to path that use set in the to attribute.
SignOut in Firebase
Basically, in Home.js we call signOut() from auth module in our logout button.
onClick={() => app.auth().signOut()}
Congratulations!!! Test your app.
npm start
Conclusion
Our application is ready. Now you’re able to use Firebase Authentication in your React application.
Thanks for reading!
Top comments (1)
Nice post! What about keep the session open to avoid user sign-in in every visit to the app?
It's great if you complete the post with that. Thanks for writing!