Skip to content
intermediatePhase 37 · Frontend Architecture

Authorization

Control access with role-based and permission-based authorization patterns.

45m
0 problems
Topic Progress0%

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

0/3solved
Build Authorization Component

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 needed
Authorization Testing

Write 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 tests
Authorization Performance

Optimize 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 analysis

Quiz

1. What is RBAC?

Question 1 options

2. How should route guards handle unauthenticated users?

Question 2 options

3. What is ownership-based authorization?

Question 3 options

4. Where should authorization checks happen?

Question 4 options

Flashcards

Question

What is Role-Based Access Control (RBAC)?

Answer

A method of restricting system access based on user roles within an organization.

Question

What is a route guard?

Answer

A component that protects routes based on authentication status and user permissions.

Question

Why check authorization on both client and server?

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?

Answer

To redirect users back to the page they were trying to access after successful authentication.

Question

What is Authorization?

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')) { ... }