- MSAL Authentication in React: Complete Guide – Part 1: Introduction and Setup
- MSAL Authentication in React: Complete Guide – Part 2: MSAL Provider and Authentication Context
- MSAL Authentication in React: Complete Guide – Part 3: Protected Routes and Route Guards
- MSAL Authentication in React: Complete Guide – Part 4: Token Management and API Calls
- MSAL Authentication in React: Complete Guide – Part 5: Advanced Topics and Production Considerations
Welcome to Part 3 of our MSAL authentication series! Now that we have our authentication provider set up, let’s implement protected routes and route guards to secure specific pages in your React application.
Understanding Protected Routes
Protected routes ensure that only authenticated users can access certain pages of your application. They act as gatekeepers, redirecting unauthenticated users to login pages or showing appropriate messages.
Types of Route Protection
- Authentication-based: User must be logged in
- Role-based: User must have specific roles or permissions
- Conditional: Based on user attributes or application state
Setting Up React Router
First, install React Router if you haven’t already:
npm install react-router-dom
Set up your basic routing structure:
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import HomePage from './pages/HomePage';
import DashboardPage from './pages/DashboardPage';
import ProfilePage from './pages/ProfilePage';
import LoginPage from './pages/LoginPage';
import ProtectedRoute from './components/ProtectedRoute';
import Navigation from './components/Navigation';
function App() {
return (
<Router>
<AuthProvider>
<div className="app">
<Navigation />
<main className="main-content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
</Routes>
</main>
</div>
</AuthProvider>
</Router>
);
}
export default App;
Creating a Protected Route Component
Let’s create a reusable ProtectedRoute component that handles authentication checks:
// src/components/ProtectedRoute.js
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useIsAuthenticated, useMsal } from '@azure/msal-react';
import LoadingSpinner from './LoadingSpinner';
const ProtectedRoute = ({
children,
redirectTo = '/login',
requiredRoles = [],
fallback = null
}) => {
const isAuthenticated = useIsAuthenticated();
const { inProgress, accounts } = useMsal();
const location = useLocation();
// Show loading during authentication process
if (inProgress === "login" || inProgress === "startup") {
return <LoadingSpinner message="Checking authentication..." />;
}
// If not authenticated, redirect to login with return URL
if (!isAuthenticated) {
return <Navigate
to={redirectTo}
state={{ from: location }}
replace
/>;
}
// Check role-based access if required roles are specified
if (requiredRoles.length > 0 && accounts.length > 0) {
const userRoles = accounts[0]?.idTokenClaims?.roles || [];
const hasRequiredRole = requiredRoles.some(role =>
userRoles.includes(role)
);
if (!hasRequiredRole) {
return fallback || (
<div className="access-denied">
<h2>Access Denied</h2>
<p>You don't have permission to access this page.</p>
</div>
);
}
}
// Render protected content
return children;
};
export default ProtectedRoute;
Advanced Route Guards
For more complex scenarios, let’s create specialized route guards:
Role-Based Route Guard
// src/components/RoleGuard.js
import React from 'react';
import { useMsal } from '@azure/msal-react';
const RoleGuard = ({
children,
allowedRoles = [],
fallback = null,
mode = 'any' // 'any' or 'all'
}) => {
const { accounts } = useMsal();
if (accounts.length === 0) {
return fallback || <div>No account information available.</div>;
}
const userRoles = accounts[0]?.idTokenClaims?.roles || [];
let hasAccess = false;
if (mode === 'any') {
// User needs at least one of the allowed roles
hasAccess = allowedRoles.some(role => userRoles.includes(role));
} else if (mode === 'all') {
// User needs all of the allowed roles
hasAccess = allowedRoles.every(role => userRoles.includes(role));
}
if (!hasAccess) {
return fallback || (
<div className="insufficient-permissions">
<h3>Insufficient Permissions</h3>
<p>You need the following roles: {allowedRoles.join(', ')}</p>
<p>Your roles: {userRoles.join(', ') || 'None'}</p>
</div>
);
}
return children;
};
export default RoleGuard;
What’s Next?
In Part 4, we’ll focus on token management and API calls. We’ll cover:
- Acquiring access tokens silently
- Making authenticated API calls
- Handling token refresh
- Token caching strategies
Continue following this series to master MSAL authentication in your React applications!