Interested in reading this tutorial as one of many chapters in my advanced React with Firebase book? Checkout the entire The Road to Firebase book that teaches you to create business web applications without the need to create a backend application with a database yourself.

In your application, users can employ an email/password combination, but also social logins to get access to your service or product. Often, the email address associated with the social logins is confirmed by the social platform (Google, Facebook, Twitter) and you know this email address really exists. But what about the email address used with the password? Because users are sometimes unwilling to provide real email addresses, they'll simply make one up, so you can't provide them with further information via email or to integrate them with third-parties where a valid email address is required. In this section, I will show you how to confirm user email addresses before they can access your application. After an email verification with a double opt-in send by email, users are authorized to use your application.

Because the Firebase API already provides this functionality, we can add it to our Firebase class to make it available for our React application. Provide an optional redirect URL that is used to navigate to the application after email confirmation:

... class Firebase { ... ... doSendEmailVerification = ( ) => this . auth . currentUser . sendEmailVerification ( { url : process . env . REACT_APP_CONFIRMATION_EMAIL_REDIRECT , } ) ; ... } export default Firebase ;

You can inline this URL, but also put it into your .env file(s). I prefer environment variables for development (.env.development) and production (.env.production). The development environment receives the localhost URL:

... REACT_APP_CONFIRMATION_EMAIL_REDIRECT = http : / / localhost : 3000

And the production environment receives an actual domain:

... REACT_APP_CONFIRMATION_EMAIL_REDIRECT = https : / / mydomain . com

That's all we need to do for the API. The best place to guide users through the email verification is during email and password sign-up:

... class SignUpFormBase extends Component { ... onSubmit = event => { ... this . props . firebase . doCreateUserWithEmailAndPassword ( email , passwordOne ) . then ( authUser => { return this . props . firebase . user ( authUser . user . uid ) . set ( { username , email , roles , } ) ; } ) . then ( ( ) => { return this . props . firebase . doSendEmailVerification ( ) ; } ) . then ( ( ) => { this . setState ( { ... INITIAL_STATE } ) ; this . props . history . push ( ROUTES . HOME ) ; } ) . catch ( error => { ... } ) ; event . preventDefault ( ) ; } ; ... } ...

Users will receive a verification email when they register for your application. To find out if a user has a verified email, you can retrieve this information from the authenticated user in your Firebase class:

... class Firebase { ... onAuthUserListener = ( next , fallback ) => this . auth . onAuthStateChanged ( authUser => { if ( authUser ) { this . user ( authUser . uid ) . once ( 'value' ) . then ( snapshot => { const dbUser = snapshot . val ( ) ; if ( ! dbUser . roles ) { dbUser . roles = { } ; } authUser = { uid : authUser . uid , email : authUser . email , emailVerified : authUser . emailVerified , providerData : authUser . providerData , ... dbUser , } ; next ( authUser ) ; } ) ; } else { fallback ( ) ; } } ) ; ... } export default Firebase ;

To protect your routes from users who have no verified email address, we will do it with a new higher-order component in src/components/Session/withEmailVerification.js that has access to Firebase and the authenticated user:

import React from 'react' ; import AuthUserContext from './context' ; import { withFirebase } from '../Firebase' ; const withEmailVerification = Component => { class WithEmailVerification extends React . Component { render ( ) { return ( < AuthUserContext.Consumer > { authUser => < Component { ... this . props } /> } </ AuthUserContext.Consumer > ) ; } } return withFirebase ( WithEmailVerification ) ; } ; export default withEmailVerification ;

Add a function in this file that checks if the authenticated user has a verified email and an email/password sign in on associated with it. If the user has only social logins, it doesn't matter if the email is not verified.

const needsEmailVerification = authUser => authUser && ! authUser . emailVerified && authUser . providerData . map ( provider => provider . providerId ) . includes ( 'password' ) ;

If this is true, don't render the component passed to this higher-order component, but a message that reminds users to verify their email addresses.

... const withEmailVerification = Component => { class WithEmailVerification extends React . Component { onSendEmailVerification = ( ) => { this . props . firebase . doSendEmailVerification ( ) ; } render ( ) { return ( < AuthUserContext.Consumer > { authUser => needsEmailVerification ( authUser ) ? ( < div > < p > Verify your E - Mail : Check you E - Mails ( Spam folder included ) for a confirmation E - Mail or send another confirmation E - Mail . </ p > < button type = " button " onClick = { this . onSendEmailVerification } > Send confirmation E - Mail </ button > </ div > ) : ( < Component { ... this . props } /> ) } </ AuthUserContext.Consumer > ) ; } } return withFirebase ( WithEmailVerification ) ; } ; export default withEmailVerification ;

Optionally, we can provide a button to resend a verification email to the user. Let's improve the user experience. After the button is clicked to resend the verification email, users should receive feedback, and be prohibited from sending another email. First, add a local state to the higher-order component that tracks whether the button was clicked:

... const withEmailVerification = Component => { class WithEmailVerification extends React . Component { constructor ( props ) { super ( props ) ; this . state = { isSent : false } ; } onSendEmailVerification = ( ) => { this . props . firebase . doSendEmailVerification ( ) . then ( ( ) => this . setState ( { isSent : true } ) ) ; } ; ... } return withFirebase ( WithEmailVerification ) ; } ; export default withEmailVerification ;

Second, show another message with a conditional rendering if a user has sent another verification email:

... const withEmailVerification = Component => { class WithEmailVerification extends React . Component { ... render ( ) { return ( < AuthUserContext.Consumer > { authUser => needsEmailVerification ( authUser ) ? ( < div > { this . state . isSent ? ( < p > E - Mail confirmation sent : Check you E - Mails ( Spam folder included ) for a confirmation E - Mail . Refresh this page once you confirmed your E - Mail . </ p > ) : ( < p > Verify your E - Mail : Check you E - Mails ( Spam folder included ) for a confirmation E - Mail or send another confirmation E - Mail . </ p > ) } < button type = " button " onClick = { this . onSendEmailVerification } disabled = { this . state . isSent } > Send confirmation E - Mail </ button > </ div > ) : ( < Component { ... this . props } /> ) } </ AuthUserContext.Consumer > ) ; } } return withFirebase ( WithEmailVerification ) ; } ; export default withEmailVerification ;

Lastly, make the new higher-order component available in your Session folder's index.js file:

import AuthUserContext from './context' ; import withAuthentication from './withAuthentication' ; import withAuthorization from './withAuthorization' ; import withEmailVerification from './withEmailVerification' ; export { AuthUserContext , withAuthentication , withAuthorization , withEmailVerification , } ;

Send a confirmation email once a user signs up with a email/password combination. You also have a higher-order component used for authorization and optionally resending a confirmation email. Next, secure all pages/routes that should be only accessible with a confirmed email. Let's begin with the home page:

import React from 'react' ; import { compose } from 'recompose' ; import { withAuthorization , withEmailVerification } from '../Session' ; const HomePage = ( ) => ( < div > < h1 > Home Page </ h1 > < p > The Home Page is accessible by every signed in user . </ p > </ div > ) ; const condition = authUser => ! ! authUser ; export default compose ( withEmailVerification , withAuthorization ( condition ) , ) ( HomePage ) ;

Next the admin page:

import React , { Component } from 'react' ; import { compose } from 'recompose' ; import { withFirebase } from '../Firebase' ; import { withAuthorization , withEmailVerification } from '../Session' ; import * as ROLES from '../../constants/roles' ; ... const condition = authUser => authUser && ! ! authUser . roles [ ROLES . ADMIN ] ; export default compose ( withEmailVerification , withAuthorization ( condition ) , withFirebase , ) ( AdminPage ) ;

And the account page:

import React , { Component } from 'react' ; import { compose } from 'recompose' ; import { AuthUserContext , withAuthorization , withEmailVerification , } from '../Session' ; import { withFirebase } from '../Firebase' ; import { PasswordForgetForm } from '../PasswordForget' ; import PasswordChangeForm from '../PasswordChange' ; ... const condition = authUser => ! ! authUser ; export default compose ( withEmailVerification , withAuthorization ( condition ) , ) ( AccountPage ) ;

All the sensitive routes for authenticated users now require a confirmed email. Finally, your application can be only used by users with real email addresses.