How to Handle Authentication in React

Published on 16.8.2019

Handling authentication is a crucial part of many web applications, and React provides great tools to build dynamic user interfaces. With the advent of React Hooks, managing state and effects has become even easier. In this post, we’ll walk through how to handle user authentication in a React app using Firebase Authentication, the new Hooks API, and useContext for managing authentication state globally.

By the end of this tutorial, you’ll know how to integrate Firebase authentication, manage user state with React Context, and control protected routes based on user authentication.

Let’s get started!


Why Firebase Authentication?

Firebase Authentication is a powerful service that allows you to easily add user authentication to your web and mobile apps. It supports various authentication methods like email/password login, Google login, Facebook login, and more. Firebase Authentication also provides simple APIs to manage and track user sessions.

For this tutorial, we’ll use Firebase’s email/password authentication, but the steps can be applied to other authentication methods as well.


Setting Up Firebase

Before we start building the authentication system, we need to set up Firebase in our project.

  1. Create a Firebase Project: Go to Firebase Console, create a new project, and enable Email/Password Authentication under the “Authentication” section.
  2. Install Firebase SDK: Install the Firebase SDK to your project:
npm install firebase
  1. Firebase Config: Create a firebase.js file to initialize Firebase with your project’s credentials.

firebase.js

import firebase from 'firebase/app';
import 'firebase/auth';

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
  messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
  appId: 'YOUR_APP_ID',
};

const firebaseApp = firebase.initializeApp(firebaseConfig);

export const auth = firebaseApp.auth();
export default firebaseApp;

Replace the placeholders with your actual Firebase project credentials, which you can find in the Firebase Console.


Setting Up Authentication Context with useContext

To manage the user’s authentication state across the application, we’ll use React’s Context API along with useContext and useState. This will allow us to keep track of the user’s authentication status and provide it globally to all components.

AuthContext.js

First, create an AuthContext to manage the authentication state.

import React, { createContext, useState, useEffect } from 'react';
import { auth } from './firebase';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(setUser);
    return unsubscribe; // Clean up the listener when the component is unmounted
  }, []);

  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  return React.useContext(AuthContext);
};

Explanation:

  • AuthContext: We create a React Context that holds the current user.
  • useState: We store the current user’s authentication state with useState.
  • useEffect: The useEffect hook listens for changes in the user’s authentication state (onAuthStateChanged) and updates the state when the user logs in or out.
  • Provider: The AuthProvider component provides the user state to the entire app.
  • useAuth Hook: This custom hook allows components to easily access the authentication state.

Handling Authentication Actions: Sign In and Sign Out

Next, let’s build components for signing in and signing out using Firebase authentication methods.

SignIn.js

import React, { useState } from 'react';
import { auth } from './firebase';
import { useHistory } from 'react-router-dom';

const SignIn = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const history = useHistory();

  const handleSignIn = async (e) => {
    e.preventDefault();
    try {
      await auth.signInWithEmailAndPassword(email, password);
      history.push('/dashboard'); // Redirect to dashboard after sign-in
    } catch (err) {
      setError('Failed to sign in. Please check your credentials.');
    }
  };

  return (
    <div>
      <h2>Sign In</h2>
      <form onSubmit={handleSignIn}>
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <input
          type="password"
          placeholder="Password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
        {error && <p>{error}</p>}
        <button type="submit">Sign In</button>
      </form>
    </div>
  );
};

export default SignIn;

SignOut.js

import React from 'react';
import { auth } from './firebase';
import { useHistory } from 'react-router-dom';

const SignOut = () => {
  const history = useHistory();

  const handleSignOut = async () => {
    await auth.signOut();
    history.push('/'); // Redirect to the home page after sign out
  };

  return (
    <div>
      <button onClick={handleSignOut}>Sign Out</button>
    </div>
  );
};

export default SignOut;

Explanation:

  • SignIn Component: We capture the user’s email and password using useState. The handleSignIn function uses Firebase’s signInWithEmailAndPassword method to authenticate the user. Upon success, we redirect the user to the dashboard.
  • SignOut Component: The handleSignOut function uses Firebase’s signOut method to log the user out and redirect them to the home page.

Protecting Routes with React Router

Now that we have our authentication setup, we need to protect certain routes that should only be accessible to logged-in users.

We’ll use React Router to manage our routing and redirect users to the sign-in page if they’re not authenticated.

App.js

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AuthProvider, useAuth } from './AuthContext';
import SignIn from './SignIn';
import SignOut from './SignOut';
import Dashboard from './Dashboard';

const ProtectedRoute = ({ children }) => {
  const { user } = useAuth();

  if (!user) {
    return <SignIn />;
  }

  return children;
};

const App = () => {
  return (
    <AuthProvider>
      <Router>
        <Switch>
          <Route path="/" exact>
            <SignIn />
          </Route>
          <Route path="/signout" exact>
            <SignOut />
          </Route>
          <Route path="/dashboard" exact>
            <ProtectedRoute>
              <Dashboard />
            </ProtectedRoute>
          </Route>
        </Switch>
      </Router>
    </AuthProvider>
  );
};

export default App;

Explanation:

  • ProtectedRoute Component: This is a custom route component that checks if the user is authenticated (via the useAuth hook). If the user is not authenticated, they are redirected to the sign-in page.
  • Routing: We set up routes for the sign-in page, sign-out page, and the protected dashboard page.

Conclusion

In this post, we learned how to:

  1. Set up Firebase Authentication in a React app.
  2. Use useContext and useState to manage authentication state globally.
  3. Create Sign In and Sign Out components using Firebase authentication methods.
  4. Protect certain routes and ensure that only authenticated users can access them.

With this setup, you have a fully functional authentication system in your React app. You can easily extend this to support other login methods like Google or Facebook, or even implement more advanced features like password resets or multi-factor authentication.

Happy coding, and enjoy building your authenticated React apps!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *