Reusable React Code Modules, Part 4 - Authentication and Authorization
Securing Your Application: Modular Approaches to Authentication and Authorization
Authentication and authorization are critical for securing web applications. This guide covers how to structure authentication and authorization modules in React, handling user roles, and securely managing sessions. We'll explore practical examples, recommended libraries, and code snippets.
Structuring the Authentication Module
Key Concepts
Authentication: Verifying the identity of a user
Authorization: Determining what resources a user can access
Roles and Permissions: Defining user roles and their access levels
Core Components
AuthProvider: Context provider to manage authentication state
PrivateRoute: Higher-order component (HOC) to protect routes
AuthService: Service for handling authentication logic
Role-based Access Control (RBAC): Managing user roles and permissions
Implementation
1. AuthProvider
The AuthProvider
component uses React Context to manage authentication state and provide it to the rest of the application
Example
import React, { createContext, useState, useContext, useEffect } from 'react';
import { getAuthState, login, logout } from './AuthService';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authState, setAuthState] = useState({ user: null, isAuthenticated: false });
useEffect(() => {
const state = getAuthState();
setAuthState(state);
}, []);
const handleLogin = async (credentials) => {
const user = await login(credentials);
setAuthState({ user, isAuthenticated: !!user });
};
const handleLogout = async () => {
await logout();
setAuthState({ user: null, isAuthenticated: false });
};
return (
<AuthContext.Provider value={{ authState, handleLogin, handleLogout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
2. PrivateRoute
The PrivateRoute
component protects routes that require authentication
Example
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { useAuth } from './AuthProvider';
const PrivateRoute = ({ component: Component, roles, ...rest }) => {
const { authState } = useAuth();
return (
<Route
{...rest}
render={props =>
authState.isAuthenticated ? (
roles && roles.indexOf(authState.user.role) === -1 ? (
<Redirect to={{ pathname: '/unauthorized' }} />
) : (
<Component {...props} />
)
) : (
<Redirect to={{ pathname: '/login' }} />
)
}
/>
);
};
export default PrivateRoute;
3. AuthService
The AuthService
handles communication with the backend for authentication
Example
import axios from 'axios';
const API_URL = 'https://example.com/api';
export const login = async (credentials) => {
const response = await axios.post(`${API_URL}/login`, credentials);
if (response.data.token) {
localStorage.setItem('token', response.data.token);
return response.data.user;
}
return null;
};
export const logout = async () => {
localStorage.removeItem('token');
};
export const getAuthState = () => {
const token = localStorage.getItem('token');
if (!token) return { user: null, isAuthenticated: false };
// Decode token and get user info (example uses JWT)
const user = jwtDecode(token);
return { user, isAuthenticated: true };
};
4. Role-Based Access Control (RBAC)
RBAC is used to restrict access based on user roles. This is managed by defining roles and checking them in the PrivateRoute
component
Example
const roles = {
admin: ['dashboard', 'admin'],
user: ['dashboard'],
};
const checkAccess = (role, page) => roles[role].includes(page);
// Usage in PrivateRoute
return roles && roles.indexOf(authState.user.role) === -1 ? (
<Redirect to={{ pathname: '/unauthorized' }} />
) : (
<Component {...props} />
);
Full Example
App Component
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AuthProvider } from './AuthProvider';
import PrivateRoute from './PrivateRoute';
import Home from './Home';
import Login from './Login';
import Dashboard from './Dashboard';
import Admin from './Admin';
import Unauthorized from './Unauthorized';
const App = () => (
<AuthProvider>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<PrivateRoute path="/dashboard" component={Dashboard} roles={['user', 'admin']} />
<PrivateRoute path="/admin" component={Admin} roles={['admin']} />
<Route path="/unauthorized" component={Unauthorized} />
</Switch>
</Router>
</AuthProvider>
);
export default App;
Login Component
import React, { useState } from 'react';
import { useAuth } from './AuthProvider';
const Login = () => {
const { handleLogin } = useAuth();
const [credentials, setCredentials] = useState({ username: '', password: '' });
const onSubmit = async (e) => {
e.preventDefault();
await handleLogin(credentials);
};
return (
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Username"
value={credentials.username}
onChange={(e) => setCredentials({ ...credentials, username: e.target.value })}
/>
<input
type="password"
placeholder="Password"
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
/>
<button type="submit">Login</button>
</form>
);
};
export default Login;