Skip to main content

Command Palette

Search for a command to run...

RBAC vs ABAC authorization

Published
View as Markdown

Authorization determines what authenticated users can access.

RBAC: Role-Based Permissions

RBAC assigns users to roles like admin, editor or viewer. Each role bundles specific permissions

Example UseCase

GitHub uses repository permission levels (read, triage, write, maintain, admin) where each level includes specific permissions like pushing code, managing issues, or configuring settings. Users inherit all permissions from their assigned level.

Code Example

// Action we can do with the resource
type Permissions = "read:notes" | "create:notes" | "update:notes" | "delete:notes"

// map action to role
const ROLES = {
  admin: ["create:notes", "update:notes", "delete:notes"],
  editor: ["create:notes", "update:notes"],
  user: ["create:notes"],
} as const;

function hasPermission(user: User, permission: Permission) {
  return ROLES[user.role].includes(permission);
}

ABAC: Attribute-Based Control

ABAC makes decisions using multiple attributes: user properties, resource characteristics, environmental factors and business rules.

This enables fine-grained, context-aware authorization

Example UseCase

  • Healthcare systems use ABAC extensively. A doctor can access patient records during their shift, in their assigned department, for patients under their care. The same doctor might be denied access to the same record outside work hours or from an untrusted location.

  • Financial services leverage ABAC for regulatory compliance. I.e: a trader can execute transactions up to their authorized limit

Code Example

// Usage
evaluatePolicy(user, {
    // context
    confidentailReport,
    workingEnvironment
}, {
    action: "read"
}
)

Another example with TS

type User = {
  id: string;
  role: "admin" | "editor" | "user";
  department: string;
};

type Article = {
  id: string;
  authorId: string;
  department: string;
  status: "draft" | "published";
};

const permissions = {
  admin: {
    articles: {
      delete: true, // Admins can delete any article
      edit: true,
    },
  },
  editor: {
    articles: {
      delete: (user: User, article: Article) =>
        article.authorId === user.id || // Their own articles
        article.department === user.department, // Their department's articles
      edit: (user: User, article: Article) =>
        article.status === "draft" && // Only drafts
        (article.authorId === user.id ||
          article.department === user.department),
    },
  },
};

function can(user: User, action: string, resource: string, data?: any) {
  const permission = permissions[user.role]?.[resource]?.[action];
  if (typeof permission === "boolean") return permission;
  if (typeof permission === "function") return permission(user, data);
  return false;
}

Now we can set up complex rules like:

  • Editors can delete their own articles.

  • Editors can edit draft articles in their department.

  • Admins can do anything.

  • Access can depend on the time of day, user location, article status, and more.

Summary

Decision factors

PatternComplexityFlexibilityUse Cases
RBACLowMediumInternal apps, clear hierarchies
ABACHighVery HighCompliance, dynamic policies
OAuthMediumMediumThird-party access, API platforms