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:
- Get the current authentication status from our
AuthContext. - If the user is authenticated, render the component it's supposed to protect (e.g., the
Checkoutpage). - If the user is not authenticated, redirect them to the
/loginpage.
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).
- Import the
ProtectedRoutecomponent. - Wrap your
Checkoutcomponent'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
ProtectedRoutecomponent acts as a reusable gatekeeper for routes that require authentication. - It uses the global
AuthContextas 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
useLocationand thestateprop, 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.