RBAC vs ABAC authorization
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
| Pattern | Complexity | Flexibility | Use Cases |
| RBAC | Low | Medium | Internal apps, clear hierarchies |
| ABAC | High | Very High | Compliance, dynamic policies |
| OAuth | Medium | Medium | Third-party access, API platforms |