Models and controllers, you will modify the React/TypeScript frontend to properly integrate with your authentication system.
1. Updated Auth Models (src/models/Auth.ts)
// Reflects your Java User and Role entities
export interface Role {
roleId: number;
roleName: string;
}
export interface User {
userId: number;
userName: string;
isActive: boolean;
roleId: number;
role: Role;
// Note: password is excluded as it shouldn't be stored in frontend
}
export interface LoginCredentials {
userName: string;
password: string;
}
export interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
// API Response structure to match your backend
export interface APIResponse<T> {
status: number;
message: string;
data: T;
timestamp: string;
}
2. Updated Auth Service (src/services/authService.ts)
import axios from 'axios';
import { LoginCredentials, User, APIResponse } from '../models/Auth';
const API_URL = 'http://localhost:9090/api';
export const authService = {
async login(credentials: LoginCredentials): Promise<{ user: User; token: string }> {
// Matches your UserController endpoint
const response = await axios.get<APIResponse<User>>(
`${API_URL}/users/${credentials.userName}&${credentials.password}`
);
if (response.data.status !== 200) {
throw new Error(response.data.message);
}
// In a real app, your backend should return a token
// For now, we'll simulate one
const token = `fake-jwt-token-for-${credentials.userName}`;
return { user: response.data.data, token };
},
// Add methods to match your API
async validateToken(token: string): Promise<User> {
// This would call your JWT verification endpoint
const response = await axios.get<APIResponse<User>>(`${API_URL}/employees`, {
headers: { Authorization: token }
});
return response.data.data;
},
logout(): Promise<void> {
return Promise.resolve();
}
};
3. Enhanced Auth Context (src/contexts/AuthContext.tsx)
import React, { createContext, useContext, useEffect, useState } from 'react';
import { authService } from '../services/authService';
import { User, AuthState, LoginCredentials } from '../models/Auth';
import { useNavigate } from 'react-router-dom';
interface AuthContextType {
authState: AuthState;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
hasPermission: (requiredRole: string) => boolean;
isOwner: (userId: number) => boolean;
}
const AuthContext = createContext<AuthContextType>({} as AuthContextType);
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [authState, setAuthState] = useState<AuthState>({
user: null,
token: null,
isAuthenticated: false,
isLoading: true,
error: null,
});
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
authService.validateToken(token)
.then(user => {
setAuthState({
user,
token,
isAuthenticated: true,
isLoading: false,
error: null,
});
})
.catch(() => {
localStorage.removeItem('token');
setAuthState(prev => ({
...prev,
isLoading: false,
isAuthenticated: false,
}));
});
} else {
setAuthState(prev => ({ ...prev, isLoading: false }));
}
}, []);
const login = async (credentials: LoginCredentials) => {
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
try {
const { user, token } = await authService.login(credentials);
localStorage.setItem('token', token);
setAuthState({
user,
token,
isAuthenticated: true,
isLoading: false,
error: null,
});
navigate(user.role.roleName === 'ADMIN' ? '/admin' : '/dashboard');
} catch (error) {
setAuthState({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: error.message,
});
throw error;
}
};
const logout = () => {
authService.logout();
localStorage.removeItem('token');
setAuthState({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
});
navigate('/login');
};
const hasPermission = (requiredRole: string): boolean => {
if (!authState.user) return false;
return authState.user.role.roleName === requiredRole;
};
const isOwner = (userId: number): boolean => {
return authState.user?.userId === userId;
};
return (
<AuthContext.Provider
value={{ authState, login, logout, hasPermission, isOwner }}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
4. Updated ProtectedRoute (src/components/ProtectedRoute.tsx)
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
interface ProtectedRouteProps {
requiredRole?: string;
ownerOnly?: boolean;
ownerId?: number;
redirectTo?: string;
}
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
requiredRole,
ownerOnly = false,
ownerId,
redirectTo = '/login'
}) => {
const { authState, hasPermission, isOwner } = useAuth();
if (authState.isLoading) {
return <div>Loading...</div>;
}
if (!authState.isAuthenticated) {
return <Navigate to={redirectTo} replace />;
}
// Role-based check
if (requiredRole && !hasPermission(requiredRole)) {
return <Navigate to="/unauthorized" replace />;
}
// Ownership check (for user-specific resources)
if (ownerOnly && ownerId !== undefined && !isOwner(ownerId)) {
return <Navigate to="/unauthorized" replace />;
}
return <Outlet />;
};
export default ProtectedRoute;
5. Updated EmployeeService with Auth (src/services/employeeService.ts)
import axios from 'axios';
import { Employee } from '../models/Employee';
import { useAuth } from '../contexts/AuthContext';
const API_URL = 'http://localhost:9090/api/employees';
// Create axios instance with auth interceptor
const api = axios.create();
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = token;
}
return config;
});
export const employeeService = {
async getEmployees(): Promise<Employee[]> {
const response = await api.get(API_URL);
return response.data;
},
async addEmployee(employee: Omit<Employee, 'empId'>): Promise<Employee> {
const response = await api.post(API_URL, employee);
return response.data;
},
// Add other CRUD operations
};
6. Updated Route Configuration (src/App.tsx)
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
import Login from './pages/Login';
import EmployeeList from './pages/EmployeeList';
import EmployeeProfile from './pages/EmployeeProfile';
import AdminDashboard from './pages/AdminDashboard';
import Unauthorized from './pages/Unauthorized';
const App: React.FC = () => {
return (
<Router>
<AuthProvider>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/unauthorized" element={<Unauthorized />} />
{/* Employee routes */}
<Route element={<ProtectedRoute requiredRole="EMPLOYEE" />}>
<Route path="/dashboard" element={<div>Employee Dashboard</div>} />
<Route path="/profile/:id" element={<EmployeeProfile />} />
</Route>
{/* Manager routes */}
<Route element={<ProtectedRoute requiredRole="MANAGER" />}>
<Route path="/employees" element={<EmployeeList />} />
</Route>
{/* Admin routes */}
<Route element={<ProtectedRoute requiredRole="ADMIN" />}>
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/admin/users" element={<div>User Management</div>} />
</Route>
{/* Owner-protected route example */}
<Route element={<ProtectedRoute ownerOnly />}>
<Route path="/my-profile" element={<EmployeeProfile />} />
</Route>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>
</AuthProvider>
</Router>
);
};
export default App;
7. Employee List with Role-Based Actions (src/pages/EmployeeList.tsx)
import React from 'react';
import { useAuth } from '../contexts/AuthContext';
import { Button } from 'react-bootstrap';
const EmployeeList: React.FC = () => {
const { authState, hasPermission } = useAuth();
return (
<div>
<h1>Employee List</h1>
{/* Only show add button for managers and admins */}
{(hasPermission('MANAGER') || hasPermission('ADMIN')) && (
<Button variant="success" className="mb-3">
Add Employee
</Button>
)}
{/* Show export button only for admins */}
{hasPermission('ADMIN') && (
<Button variant="secondary" className="ms-2">
Export Data
</Button>
)}
{/* Table content */}
<table className="table">
{/* ... */}
<tbody>
{employees.map(emp => (
<tr key={emp.empId}>
<td>{emp.empName}</td>
{/* ... other columns */}
<td>
{/* Edit button for managers+ */}
{(hasPermission('MANAGER') || hasPermission('ADMIN')) && (
<Button variant="primary" size="sm">
Edit
</Button>
)}
{/* Delete button only for admins */}
{hasPermission('ADMIN') && (
<Button variant="danger" size="sm" className="ms-2">
Delete
</Button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default EmployeeList;
Key Integration Points with Your Backend:
Authentication Flow:
Matches your
UserController
endpoint (/users/{userName}&{password}
)Expects an
APIResponse<User>
structure in return
Authorization Header:
Uses the token in the Authorization header as your
EmployeesController
expectsMatches the
@RequestHeader(value="authorization")
parameter
Role-Based Access:
Uses the
role.roleName
from yourUser
entitySupports your
Role
entity structure withroleId
androleName
User Context:
Maintains the
userId
andisActive
status from yourUser
entity
Recommended Backend Modifications:
JWT Implementation:
Your backend should return a proper JWT token on login
Add a
/validate-token
endpoint for frontend token validation
Enhanced Role System:
Consider adding permissions/privileges to roles
Add endpoint to fetch all roles for user management
Password Security:
Never return passwords in API responses
Ensure passwords are properly hashed server-side
This implementation provides a complete integration with your backend while maintaining strong TypeScript typing and React best practices. The system supports:
User authentication against your existing endpoint
Role-based access control using your Role entity
Token-based authorization for API calls
Ownership checks for user-specific resources
Comprehensive type safety throughout the application
No comments:
Post a Comment