Introduction: Why Authentication Context Matters

Welcome back! In the last few lessons, you learned how to set up an API client, build a login form, protect routes, and create a registration flow. Now, you are ready to make your authentication logic more organized and accessible throughout your React app.

In a real-world application, you often need to know whether a user is logged in or not in many different places — like the navigation bar, protected pages, or even when making API requests. If you try to pass this information down through props, your code can get messy and hard to manage. This is where React Context comes in. By using an authentication context, you can keep track of the user's login state and make it available anywhere in your app without having to pass it through every component.

Example: Prop Drilling vs. Context

To see why context helps, let’s look at a simple example. Imagine you have a navigation bar deep inside your app that needs to know whether the user is logged in and how to log them out.

❌ Bad Approach: Prop Drilling Here, isAuthenticated and logout are passed through multiple components, even if only the deepest one needs them:

Notice how isAuthenticated and logout get passed through Layout even though Layout doesn’t use them. As your app grows, this “prop drilling” gets messy.

✅ Better Approach: Using Context

With AuthContext, you provide authentication state and actions once at the top level. Any component can access them directly without threading props everywhere.

Understanding React Context For Authentication

React Context is a way to share data across your app without having to pass props down manually at every level. Think of it as a shared key that any component can use to check if the user is logged in, log them out, or update their authentication status.

For example, if you have a navigation bar and a protected page, both can use the same context to know if the user is authenticated. This keeps your code clean and avoids duplication.

When building a React app, different parts of your UI often need to know whether a user is logged in. For example, your navigation bar might need to show “Log out” instead of “Log in,” while your profile page needs access to the current user’s details. Without context, you would have to pass this information down as props through every intermediate component — a process called prop drilling. This quickly becomes messy, repetitive, and error-prone.

React Context solves this problem by acting as a shared store that any component can tap into, no matter how deep in the tree it is. With an AuthContext, we create one central place that holds authentication data and makes it available everywhere in the app.

  • What the AuthContext provides: At its core, the authentication context stores important state like isAuthenticated, the user’s token, or even user profile info. Alongside the state, it also provides actions such as login(token) and logout(). This makes it a single source of truth for authentication, so when the token changes, the rest of the app automatically knows whether the user is logged in or not. Typically, the token is also saved in localStorage so that the state can be restored even after a page refresh.

  • Why this is better than props: Instead of manually passing isAuthenticated and logout down through every layer of your app, any component can directly “subscribe” to the context. This keeps your code cleaner and makes it easier to maintain as your app grows.

App Bootstrap & Provider Placement (Using AuthContext Across Routes)

To use AuthContext, the provider must wrap the entire subtree that calls useAuth()—that includes your routed pages and guards. The simplest, reliable setup is to wrap the RouterProvider with AuthProvider at the root:

  • This guarantees that all routes, layout components, and utilities like ProtectedRoute can access useAuth().
  • If AuthProvider is mounted inside a page instead, anything outside that page (e.g., other routes or top-level nav) won’t see auth state.
  • Order matters only in terms of ancestry: the provider must be an ancestor of consumers. Wrapping RouterProvider is the most straightforward way to ensure that.

Below is a minimal example for your entry point.

Why this placement works: Every routed screen and navigation element is now a child of AuthProvider, so is available everywhere—header, pages, protected routes, etc.

Step-by-Step: Building the AuthContext

Let’s build the authentication context step by step. We’ll create a context, a provider, and a custom hook to use the context. We’ll also handle login and logout actions.

Here’s the main code for the authentication context:

Let’s break down what’s happening here:

  • AuthContext: This is the context object that will hold our authentication state and actions.
  • AuthProvider: This component wraps your app and provides authentication state to all its children.
    • It uses useState to keep track of the authentication token.
    • The useEffect sets up an interceptor so that every API request includes the token if it exists.
    • The login function saves the token, updates the state, and navigates to the user’s shelf.
    • The logout function removes the token, updates the state, and navigates to the home page.
Using AuthContext In Your App

Now that you have the authentication context, let’s see how you can use it in your components. For example, you might want to show different navigation links depending on whether the user is logged in.

Here’s how you can use the context in your main app component:

Explanation:

  • The useAuth hook gives you access to and .
LoginForm (Revisited): Using login() from useAuth

We previously built the login component. Everything should look familiar except the call to login(token)—that’s what wires the UI to the context you just created. Here is the full component you provided; we’ll annotate how useAuth().login is used.

How login(token) integrates

  • After a successful POST to /auth/login, we extract the token from the backend envelope.
  • login(token):
Summary And What’s Next

In this lesson, you learned how to create an authentication context in React. You saw how to set up the context, provide it to your app, and use it in your components to manage login state and protect routes. This approach keeps your authentication logic organized and easy to use anywhere in your app.

  • Provider placement: Wrap RouterProvider with AuthProvider at the root so every routed component can call useAuth().
  • Context design: Keep AuthContext small and stable—isAuthenticated, login, logout to minimize re-renders.
  • Consumption: Extract { isAuthenticated, logout } from useAuth() for conditional nav and use a ProtectedRoute to guard screens.
  • Login integration: Call login(token) after successful authentication to persist and broadcast auth state; the UI updates automatically.

You are now ready to practice using the authentication context in real code. In the next exercises, you will get hands-on experience with these concepts, such as updating navigation based on authentication and protecting routes. This will help you build more secure and user-friendly applications. Keep up the great work!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal