Creating Authentication Context

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:

// App.tsx
export default function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const login = () => setIsAuthenticated(true);
  const logout = () => setIsAuthenticated(false);

  return (
    <Layout
      isAuthenticated={isAuthenticated}
      logout={logout}
    />
  );
}

// Layout.tsx
export function Layout({ isAuthenticated, logout }) {
  return (
    <div>
      <Header isAuthenticated={isAuthenticated} logout={logout} />
    </div>
  );
}

// Header.tsx
export function Header({ isAuthenticated, logout }) {
  return (
    <nav>
      {isAuthenticated ? (
        <button onClick={logout}>Log out</button>
      ) : (
        <span>Please log in</span>
      )}
    </nav>
  );
}

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.

// AuthContext.tsx
import React, { createContext, useContext, useState } from 'react';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const login = () => setIsAuthenticated(true);
  const logout = () => setIsAuthenticated(false);

  const value = { isAuthenticated, login, logout };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

// Custom hook for easy access
export const useAuth = () => useContext(AuthContext);
// App.tsx
export default function App() {
  return (
    <AuthProvider>
      <Layout />
    </AuthProvider>
  );
}

// Layout.tsx
export function Layout() {
  return (
    <div>
      <Header />
    </div>
  );
}

// Header.tsx
import { useAuth } from './AuthContext';

export function Header() {
  const { isAuthenticated, logout } = useAuth();

  return (
    <nav>
      {isAuthenticated ? (
        <button onClick={logout}>Log out</button>
      ) : (
        <span>Please log in</span>
      )}
    </nav>
  );
}

With this setup, only the component that actually needs the authentication data consumes it. You don’t have to manually pass isAuthenticated and logout through every intermediate layer.

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