Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 87

Protecting the Checkout Route

Recommended resources

Hello! Welcome back.

In our last lesson, you successfully wired up your React login and registration forms to your Laravel backend. Users can now create accounts and log in, and your AuthContext correctly tracks their authentication state. This is a huge milestone!

Now, we'll leverage that authentication state to secure parts of our application. Today's goal is to protect the checkout route so it is only accessible to authenticated users. We'll create a special "gatekeeper" component in React that checks if a user is logged in before letting them see a specific page.

A Note on Security

Before we start, it's important to frame this correctly. Protecting a route on the client-side (in React) is primarily a User Experience (UX) feature. It prevents non-logged-in users from seeing pages they can't use.

However, the real security for your application's data must always be enforced on your Laravel backend. For example, when the checkout form is submitted, your Laravel API must re-verify that the user is authenticated before processing an order. As a backend developer, you know that any client-side code can be manipulated.

Protected Routes and Authentication with React Router - Fireship

The article 'Protected Routes and Authentication with React Router' from Fireship starts with an excellent warning about this very topic. It's a great reminder of the division of responsibilities between frontend and backend.

Please read the introduction and the 'Warning' section. It reinforces the idea that client-side route protection is for UX, while server-side checks are for actual security.


1. The Protected Route Component Pattern

The most common way to protect a route in React Router is to create a wrapper component. Let's call it ProtectedRoute. Its logic will be simple:

  1. Get the current authentication status from our AuthContext.
  2. If the user is authenticated, render the component it's supposed to protect (e.g., the Checkout page).
  3. If the user is not authenticated, redirect them to the /login page.

Let's create this component.

Create a new file: src/components/ProtectedRoute.jsx

import React from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext'; // Make sure this path is correct

const ProtectedRoute = ({ children }) => {
    const { user } = useAuth(); // Get the user from your AuthContext

    if (!user) {
        // If there is no user, redirect to the login page
        return <Navigate to="/login" />;
    }

    // If the user is authenticated, render the children components
    return children;
};

export default ProtectedRoute;

This component is elegant and reusable. It uses the user object from our AuthContext as the single source of truth. If user exists, it renders its children. If not, it uses the <Navigate> component from React Router to perform a client-side redirect.


2. Applying the ProtectedRoute

Now, let's use our new component to protect the /checkout route. Open your main routing file (likely App.jsx).

  1. Import the ProtectedRoute component.
  2. Wrap your Checkout component's route with it.

Your routes might look something like this:

// In App.jsx or your main router file

import { Routes, Route } from 'react-router-dom';
import Homepage from './pages/Homepage';
import Login from './pages/Login';
import Checkout from './pages/Checkout';
import ProtectedRoute from './components/ProtectedRoute'; // 1. Import

// ... other imports

function App() {
    return (
        <Routes>
            {/* Public Routes */}
            <Route path="/" element={<Homepage />} />
            <Route path="/login" element={<Login />} />

            {/* Protected Routes */}
            <Route
                path="/checkout"
                element={
                    // 2. Wrap the component
                    <ProtectedRoute>
                        <Checkout />
                    </ProtectedRoute>
                }
            />
            
            {/* ... other routes ... */}
        </Routes>
    );
}

With this change, if a user who isn't logged in tries to navigate to /checkout (either by clicking a link or typing the URL), they will be immediately redirected to /login.

The video below demonstrates this exact pattern of creating a RequireAuth component (same concept as our ProtectedRoute) and applying it to a route.

React Router Tutorial - 15 - Authentication and Protected Routes

The 'React Router Tutorial - Authentication and Protected Routes' video from Codevolution provides a clear, step-by-step walkthrough of this entire process.

Please watch the segment on creating the require auth component (06:29 - 11:23, but the key part is from 12:14 onwards). Notice how the component checks for the user and either returns the children or the <Navigate> component. Then, see how it's used to wrap the Profile component in App.js. This is exactly what we just did.


3. Improving UX: Redirecting Back After Login

Our current setup has a small UX flaw. If a user tries to go to /checkout, gets redirected, and then logs in, they'll land on whatever page your Login component redirects to by default (e.g., the homepage). The user has forgotten what they were originally trying to do!

We can fix this by "remembering" the page they wanted to visit.

Step 1: Pass the Original Location from ProtectedRoute

We'll use the useLocation hook to get the page the user was on and pass it to the /login route via the state prop on <Navigate>.

Update src/components/ProtectedRoute.jsx:

import React from 'react';
// 1. Import useLocation
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

const ProtectedRoute = ({ children }) => {
    const { user } = useAuth();
    // 2. Get the current location
    const location = useLocation();

    if (!user) {
        // 3. Pass the location in state
        // The `replace` prop is a nice touch: it replaces the `/checkout` entry
        // in the history stack with `/login`, so the user can't click "back"
        // to get to the protected page.
        return <Navigate to="/login" state={{ from: location }} replace />;
    }

    return children;
};

export default ProtectedRoute;

Step 2: Use the Location in the Login Component

Now, your Login component can check for this state. If it exists, it will redirect the user back to their intended page after a successful login.

Update src/pages/Login.jsx:

// In your login component...
import { useNavigate, useLocation } from 'react-router-dom';
// ... other imports like useState, useAuth, apiClient

function Login() {
    const navigate = useNavigate();
    // 1. Get the location object
    const location = useLocation();
    const { login } = useAuth();
    
    // 2. Determine the redirect path
    const from = location.state?.from?.pathname || "/"; // Default to homepage

    const handleSubmit = async (e) => {
        e.preventDefault();
        // ... your existing error handling and state ...

        try {
            await apiClient.get('/sanctum/csrf-cookie');
            const response = await apiClient.post('/api/login', { email, password });

            if (response.status === 200) {
                login(response.data); // Update global auth state
                // 3. Navigate to the stored path
                navigate(from, { replace: true }); 
            }
        } catch (error) {
            // ... your error handling ...
        }
    };
    
    // ... rest of your component JSX
}

This simple change dramatically improves the user flow. Both the Fireship article and the Codevolution video cover this exact technique.

React Router Tutorial - 15 - Authentication and Protected Routes

Let's revisit the Codevolution video to see this 'redirect back' logic in action.

Watch the section on improvements (14:42 - 17:38). It shows precisely how to use useLocation and the state prop to pass the original path and then how to retrieve and use it in the Login component.


Conclusion

Great work! You've implemented one of the most fundamental features of any web application that has user accounts.

Key Takeaways:

  • A ProtectedRoute component acts as a reusable gatekeeper for routes that require authentication.
  • It uses the global AuthContext as the single source of truth for the user's login status.
  • The <Navigate> component from React Router is used to perform client-side redirects.
  • By using useLocation and the state prop, you can create a seamless UX by redirecting users back to their originally intended page after they log in.

Preview of the Next Lesson:

Now that your checkout page is secure, the next step is to actually build it! In the next lesson, we will focus on building a multi-step checkout form for shipping and payment information.