Role-Based Access
Role-Based Access Control
RBAC restricts access based on user roles within an organization.
Role Definitions
// types/roles.js
export const Roles = {
ADMIN: 'admin',
MANAGER: 'manager',
EDITOR: 'editor',
VIEWER: 'viewer',
};
export const Permissions = {
CREATE_POST: 'create:post',
EDIT_POST: 'edit:post',
DELETE_POST: 'delete:post',
PUBLISH_POST: 'publish:post',
MANAGE_USERS: 'manage:users',
VIEW_ANALYTICS: 'view:analytics',
};
// Role-to-permission mapping
export const RolePermissions = {
[Roles.ADMIN]: Object.values(Permissions),
[Roles.MANAGER]: [
Permissions.CREATE_POST,
Permissions.EDIT_POST,
Permissions.DELETE_POST,
Permissions.PUBLISH_POST,
Permissions.VIEW_ANALYTICS,
],
[Roles.EDITOR]: [
Permissions.CREATE_POST,
Permissions.EDIT_POST,
Permissions.VIEW_ANALYTICS,
],
[Roles.VIEWER]: [
Permissions.VIEW_ANALYTICS,
],
};
Authorization Hook
// hooks/useAuthorization.js
export function useAuthorization() {
const { user } = useAuth();
const hasRole = (role) => {
return user?.role === role;
};
const hasAnyRole = (roles) => {
return roles.includes(user?.role);
};
const hasPermission = (permission) => {
if (!user?.role) return false;
const permissions = RolePermissions[user.role] || [];
return permissions.includes(permission);
};
const hasAnyPermission = (permissions) => {
return permissions.some(p => hasPermission(p));
};
return { hasRole, hasAnyRole, hasPermission, hasAnyPermission };
}
// Usage
function AdminPanel() {
const { hasRole } = useAuthorization();
if (!hasRole(Roles.ADMIN)) {
return <AccessDenied />;
}
return <div>Admin Dashboard</div>;
}
Component-Level Authorization
// components/Authorized.jsx
function Authorized({ roles, permissions, fallback, children }) {
const { hasAnyRole, hasAnyPermission } = useAuthorization();
const isAuthorized =
(!roles || hasAnyRole(roles)) &&
(!permissions || hasAnyPermission(permissions));
if (!isAuthorized) {
return fallback || null;
}
return children;
}
// Usage
<Authorized roles={[Roles.ADMIN, Roles.MANAGER]}>
<DeleteButton />
</Authorized>
<Authorized
permissions={[Permissions.EDIT_POST]}
fallback={<span>Read only</span>}
>
<EditForm />
</Authorized>
Permission Patterns
Permission Patterns
Resource-Based Permissions
Define permissions based on resources and actions:
// utils/permissions.js
export function canPerform(user, action, resource) {
const permissions = {
post: {
create: [Roles.ADMIN, Roles.MANAGER, Roles.EDITOR],
read: [Roles.ADMIN, Roles.MANAGER, Roles.EDITOR, Roles.VIEWER],
update: [Roles.ADMIN, Roles.MANAGER, Roles.EDITOR],
delete: [Roles.ADMIN, Roles.MANAGER],
publish: [Roles.ADMIN, Roles.MANAGER],
},
user: {
create: [Roles.ADMIN],
read: [Roles.ADMIN, Roles.MANAGER],
update: [Roles.ADMIN],
delete: [Roles.ADMIN],
},
analytics: {
read: [Roles.ADMIN, Roles.MANAGER, Roles.EDITOR],
},
};
const allowedRoles = permissions[resource]?.[action];
if (!allowedRoles) return false;
return allowedRoles.includes(user.role);
}
// Usage in API calls
async function deletePost(postId, user) {
if (!canPerform(user, 'delete', 'post')) {
throw new Error('Insufficient permissions');
}
return fetch(`/api/posts/${postId}`, { method: 'DELETE' });
}
Ownership-Based Permissions
Allow users to modify only their own resources:
export function isOwner(user, resource) {
return user?.id === resource?.authorId || user?.id === resource?.userId;
}
export function canModify(user, resource) {
// Admins can modify anything
if (user.role === Roles.ADMIN) return true;
// Managers can modify most things
if (user.role === Roles.MANAGER) return true;
// Others can only modify their own
return isOwner(user, resource);
}
// Component usage
function EditButton({ post, user }) {
const canEdit = canModify(user, post);
return (
<button disabled={!canEdit}>
Edit
</button>
);
}
Conditional Rendering
// components/FeatureFlags.jsx
function FeatureGate({ feature, children, fallback }) {
const { user } = useAuth();
const features = useFeatures();
const isEnabled =
features.isEnabled(feature) &&
(!feature.requiredRole || user?.role === feature.requiredRole);
return isEnabled ? children : (fallback || null);
}
// Usage
<FeatureGate feature="beta-dashboard" fallback={<ComingSoon />}>
<BetaDashboard />
</FeatureGate>
Route Guards
Route Guards
Protect routes based on authentication and authorization.
Protected Route Component
// components/ProtectedRoute.jsx
function ProtectedRoute({
children,
requiredRoles,
requiredPermissions,
redirectTo = '/login'
}) {
const { user, loading } = useAuth();
const { hasAnyRole, hasAnyPermission } = useAuthorization();
const location = useLocation();
if (loading) {
return <LoadingSpinner />;
}
if (!user) {
return <Navigate to={redirectTo} state={{ from: location }} replace />;
}
if (requiredRoles && !hasAnyRole(requiredRoles)) {
return <Navigate to="/unauthorized" replace />;
}
if (requiredPermissions && !hasAnyPermission(requiredPermissions)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// Usage in Router
function AppRouter() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/admin/*"
element={
<ProtectedRoute requiredRoles={[Roles.ADMIN]}>
<AdminLayout />
</ProtectedRoute>
}
/>
<Route
path="/posts/:id/edit"
element={
<ProtectedRoute requiredPermissions={[Permissions.EDIT_POST]}>
<EditPost />
</ProtectedRoute>
}
/>
</Routes>
);
}
Layout Protection
// layouts/AdminLayout.jsx
function AdminLayout() {
const { user } = useAuth();
const { hasRole } = useAuthorization();
if (!hasRole(Roles.ADMIN)) {
return <Navigate to="/dashboard" replace />;
}
return (
<div className="admin-layout">
<AdminSidebar />
<main>
<Routes>
<Route path="users" element={<UserManagement />} />
<Route path="settings" element={<Settings />} />
<Route path="analytics" element={<Analytics />} />
</Routes>
</main>
</div>
);
}
Redirect After Login
function LoginPage() {
const { login } = useAuth();
const location = useLocation();
const navigate = useNavigate();
const from = location.state?.from?.pathname || '/dashboard';
const handleSubmit = async (e) => {
e.preventDefault();
await login(email, password);
navigate(from, { replace: true });
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
</form>
);
}
Practice Problems
Create a reusable React component implementing Authorization. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Authorization using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Authorization for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is RBAC?
2. How should route guards handle unauthenticated users?
3. What is ownership-based authorization?
4. Where should authorization checks happen?
Flashcards
Question
What is Role-Based Access Control (RBAC)?
Click to reveal answer
Answer
A method of restricting system access based on user roles within an organization.
Question
What is a route guard?
Click to reveal answer
Answer
A component that protects routes based on authentication status and user permissions.
Question
Why check authorization on both client and server?
Click to reveal answer
Answer
Client checks improve UX; server checks ensure security since client code can be bypassed.
Question
What is the purpose of storing the return URL after login redirect?
Click to reveal answer
Answer
To redirect users back to the page they were trying to access after successful authentication.
Question
What is Authorization?
Click to reveal answer
Answer
Authorization is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.RBAC restricts access based on user roles within an organization
- 2.Route guards protect UI routes and redirect unauthorized users
- 3.Always check authorization on both client and server
- 4.Ownership-based permissions allow users to modify their own resources
- 5.Store return URL for redirect after login
Interview Tips
- •Explain how you would implement RBAC in a frontend application
- •Discuss why authorization must be checked server-side even with client checks
- •Describe how to handle permission changes in real-time
Cheat Sheet
Authorization Cheat Sheet
RBAC Pattern
const RolePermissions = {
admin: ['create', 'read', 'update', 'delete'],
editor: ['create', 'read', 'update'],
viewer: ['read']
};
Route Guard
<ProtectedRoute requiredRoles={['admin']}>
<AdminPage />
</ProtectedRoute>
Permission Hook
const { hasRole, hasPermission } = useAuthorization();
if (hasRole('admin')) { ... }