Implementation of Secure Route in ReactJS
Approach using React Router v6 along with some custom hooks for handling authentication
- Create a
PrivateRoute
component: This will be a higher-order component (HOC) that checks if a user is authenticated. - Admin-specific checks: Extend the
PrivateRoute
for admin routes to check if the user has admin privileges. - Mock Authentication Context: For simplicity, use a mock authentication context. In a real app, you’d replace this with actual authentication logic.
Authentication Context Setup
You will need a context that holds the user’s authentication state.
// AuthContext.js
import React, { createContext, useContext, useState } from 'react';
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
const isAuthenticated = !!user;
return (
<AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
Create PrivateRoute
for Securing Routes
// PrivateRoute.js
import { Navigate } from 'react-router-dom';
import { useAuth } from './AuthContext';
export const PrivateRoute = ({ children }) => {
const { isAuthenticated } = useAuth();
return isAuthenticated ? children : <Navigate to="/login" />;
};
export const AdminRoute = ({ children }) => {
const { user } = useAuth();
return user?.role === 'admin' ? children : <Navigate to="/login" />;
};
Modify the App
Component to Use PrivateRoute
and AdminRoute
import { Route, Routes } from 'react-router-dom';
import Footer from './components/Footer';
import NavBar from './components/NavBar';
import * as Pages from './pages/_index';
import * as Admin from './admin/_index';
import { useTranslation } from 'react-i18next';
import { useEffect } from 'react';
import { PrivateRoute, AdminRoute } from './PrivateRoute';
import { AuthProvider } from './AuthContext';
export default function App() {
const { i18n } = useTranslation();
useEffect(() => {
let lang;
if (localStorage.i18nextLng !== undefined) {
lang = localStorage.i18nextLng;
console.log(`Sprache '${lang}' von localStorage`);
} else if (document.location.host.endsWith('.com')) {
lang = 'en';
console.log(`Sprache '${lang}' von document.location.host`);
} else if (navigator.language !== undefined) {
lang = navigator.language.substring(0, 2);
console.log(`Sprache '${lang}' von navigator.language`);
} else {
i18n.changeLanguage('en');
console.log(`Default Sprache '${lang}'`);
}
localStorage.i18nextLng = lang;
i18n.changeLanguage(lang);
}, []);
return (
<AuthProvider>
<NavBar />
<Routes>
<Route path='/' element={<Pages.Home />} />
<Route path='services' element={<Pages.Services />} />
<Route path='pricing' element={<Pages.Pricing />} />
<Route path='about' element={<Pages.About />} />
<Route path='login' element={<Pages.Login />} />
<Route path='register' element={<Pages.Register />} />
<Route path='contact' element={<Pages.Contact />} />
<Route path='policy' element={<Pages.Policy />} />
<Route path='*' element={<Pages.NotFound />} />
{/* Private Route for Authenticated Users */}
<Route
path='dashboard'
element={
<PrivateRoute>
<Admin.Dashboard />
</PrivateRoute>
}
/>
{/* Admin-only Route */}
<Route
path='admin/dashboard'
element={
<AdminRoute>
<Admin.AdminDashboard />
</AdminRoute>
}
/>
</Routes>
<Footer />
</AuthProvider>
);
}
Explanation:
- AuthProvider: This component provides authentication state to the rest of the app. The state includes user information and authentication status.
- PrivateRoute: Wraps routes that require authentication. If the user is not authenticated, they are redirected to the login page.
- AdminRoute: Wraps routes that require the user to be an admin. If the user does not have the appropriate role, they are redirected to the login page.
- Usage in App: Both
PrivateRoute
andAdminRoute
are used to protect specific routes. For example, thedashboard
route is protected byPrivateRoute
, while theadmin/dashboard
route is protected byAdminRoute
.
Secure Route in Multiple User Role
Expand the AuthContext
to Handle Multiple Roles
// AuthContext.js
import React, { createContext, useContext, useState } from 'react';
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
const isAuthenticated = !!user;
return (
<AuthContext.Provider value={{ user, login, logout, isAuthenticated }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
Create Role-based PrivateRoute
Components
// PrivateRoute.js
import { Navigate } from 'react-router-dom';
import { useAuth } from './AuthContext';
export const PrivateRoute = ({ children }) => {
const { isAuthenticated } = useAuth();
return isAuthenticated ? children : <Navigate to="/login" />;
};
export const AdminRoute = ({ children }) => {
const { user } = useAuth();
return user?.role === 'admin' ? children : <Navigate to="/login" />;
};
export const VolunteerRoute = ({ children }) => {
const { user } = useAuth();
return user?.role === 'volunteer' ? children : <Navigate to="/login" />;
};
export const OrganizerRoute = ({ children }) => {
const { user } = useAuth();
return user?.role === 'organizer' ? children : <Navigate to="/login" />;
};
Modify the App
Component to Use the New Routes
import { Route, Routes } from 'react-router-dom';
import Footer from './components/Footer';
import NavBar from './components/NavBar';
import * as Pages from './pages/_index';
import * as Admin from './admin/_index';
import * as Volunteer from './volunteer/_index';
import * as Organizer from './organizer/_index';
import { useTranslation } from 'react-i18next';
import { useEffect } from 'react';
import { PrivateRoute, AdminRoute, VolunteerRoute, OrganizerRoute } from './PrivateRoute';
import { AuthProvider } from './AuthContext';
export default function App() {
const { i18n } = useTranslation();
useEffect(() => {
let lang;
if (localStorage.i18nextLng !== undefined) {
lang = localStorage.i18nextLng;
console.log(`Sprache '${lang}' von localStorage`);
} else if (document.location.host.endsWith('.com')) {
lang = 'en';
console.log(`Sprache '${lang}' von document.location.host`);
} else if (navigator.language !== undefined) {
lang = navigator.language.substring(0, 2);
console.log(`Sprache '${lang}' von navigator.language`);
} else {
i18n.changeLanguage('en');
console.log(`Default Sprache '${lang}'`);
}
localStorage.i18nextLng = lang;
i18n.changeLanguage(lang);
}, []);
return (
<AuthProvider>
<NavBar />
<Routes>
<Route path='/' element={<Pages.Home />} />
<Route path='services' element={<Pages.Services />} />
<Route path='pricing' element={<Pages.Pricing />} />
<Route path='about' element={<Pages.About />} />
<Route path='login' element={<Pages.Login />} />
<Route path='register' element={<Pages.Register />} />
<Route path='contact' element={<Pages.Contact />} />
<Route path='policy' element={<Pages.Policy />} />
<Route path='*' element={<Pages.NotFound />} />
{/* Private Route for Authenticated Users */}
<Route
path='dashboard'
element={
<PrivateRoute>
<Pages.Dashboard />
</PrivateRoute>
}
/>
{/* Admin-only Route */}
<Route
path='admin/dashboard'
element={
<AdminRoute>
<Admin.AdminDashboard />
</AdminRoute>
}
/>
{/* Volunteer-only Route */}
<Route
path='volunteer/dashboard'
element={
<VolunteerRoute>
<Volunteer.VolunteerDashboard />
</VolunteerRoute>
}
/>
{/* Organizer-only Route */}
<Route
path='organizer/dashboard'
element={
<OrganizerRoute>
<Organizer.OrganizerDashboard />
</OrganizerRoute>
}
/>
</Routes>
<Footer />
</AuthProvider>
);
}
Explanation:
- AuthProvider: The context provider now allows managing the user’s role. You can add a
role
field to theuser
object when a user logs in. - Role-based Private Routes: Each role (admin, volunteer, and organizer) has a separate route guard (
AdminRoute
,VolunteerRoute
,OrganizerRoute
) that checks if the user has the required role. - Secure Routes: In the
App
component, you wrap the routes that are specific toadmin
,volunteer
, andorganizer
roles with their corresponding role-based route guards. - Fallback to Login: If a user tries to access a protected route without the correct role, they will be redirected to the login page.
Example User Object in AuthContext
When logging in a user, you can set the user
object with the required role. For example:
// Example login function in AuthContext.js
const login = (userData) => setUser({ ...userData, role: 'volunteer' });
This ensures that each user has a role, and your route guards will check against that role to allow or deny access to specific routes.
Notes:
- Authorization Logic: In a real application, you would fetch the user’s role and permissions from a server or use a library like
Firebase
,Auth0
, or similar. - Session Management: Make sure to handle user sessions properly and securely, including token management if you are using JWT for authentication.