Principal-based access control lets you create different permission levels for different team members. Instead of giving everyone the same access, you can specify that only developers can modify source code, only designers can access design files, or only admins can change production configurations.
This document shows how to use the principal
property in your ACL rules to implement role-based permissions and evaluate those permissions programmatically in your code.
To get the most out of this document, make sure you're familiar with Access Control List (ACL) basics.
The principal
property lets you restrict rules to specific teams or roles. This enables fine-grained access control based on user membership in various groups.
- Teams and roles: each user can belong to multiple teams, such as
["developer", "admin"]
. - Rule matching: rules only apply if the user has at least one matching principal.
- No principal: rules without principals apply to all users.
- Default behavior: if a user has no matching principals for any rule, access is denied.
Example principals:
["admin"]
: only applies to admin users["developer", "designer"]
: applies to developers or designers[]
orundefined
: applies to all users (default behavior)
As an example, note the different access levels for different team members in the json
below:
{
"accessControl": {
"entries": [
{
"action": "allow",
"resource": "/docs/**",
"permissions": ["read"],
"comment": "Everyone can read documentation"
},
{
"action": "allow",
"resource": "/docs/**",
"permissions": ["write"],
"principal": ["editor", "admin"],
"comment": "Only editors and admins can write docs"
},
{
"action": "allow",
"resource": "/src/**",
"permissions": ["read", "write"],
"principal": ["developer", "admin"],
"comment": "Developers and admins can access source code"
},
{
"action": "allow",
"resource": "/design/**",
"permissions": ["read", "write"],
"principal": ["designer", "admin"],
"comment": "Designers and admins can access design files"
},
{
"action": "deny",
"resource": "/src/production/**",
"permissions": ["write"],
"principal": ["developer"],
"failMessage": "Developers cannot modify production code - admin approval required"
},
{
"action": "deny",
"resource": "/admin/**",
"permissions": ["read", "write", "list"],
"principal": ["developer", "designer"],
"failMessage": "Admin area is restricted to administrators only"
}
]
}
}
In this setup:
- Everyone can read documentation.
- Editors and admins can modify documentation.
- Developers and admins can work with source code.
- Designers and admins can access design files.
- Developers cannot modify production code (even if they're also admins).
Handle users with multiple roles, for example, a developer who is also an admin:
{
"accessControl": {
"entries": [
{
"action": "allow",
"resource": "/dev/**",
"permissions": ["read", "write"],
"principal": ["developer"]
},
{
"action": "allow",
"resource": "/admin/**",
"permissions": ["read", "write"],
"principal": ["admin"]
},
{
"action": "deny",
"resource": "/admin/critical/**",
"permissions": ["write"],
"principal": ["developer"],
"failMessage": "Developer role cannot modify critical admin files, even with admin privileges"
}
]
}
}
In this example, a user with both ["developer", "admin"]
roles would:
- Have access to
/dev/**
files through thedeveloper
role - Have access to
/admin/**
files through theadmin
role - Be denied write access to
/admin/critical/**
(deny rule for developer takes precedence)
When working with team-based ACLs, you often need to evaluate permissions dynamically in your code. This is essential for:
- Showing or hiding UI elements based on user roles
- Authorizing API calls before making requests
- Implementing custom business logic that depends on user permissions
- Debugging access issues by checking what rules apply to specific users
You can also evaluate ACL rules programmatically:
import { evaluateAccess } from "$/vcp-common/acl";
import type { AclPolicy } from "@builder.io/ai-utils";
const policy: AclPolicy = {
entries: [
{
action: "allow",
resource: "/docs/**",
permissions: ["read", "write"]
},
{
action: "deny",
resource: "/docs/secret.md",
permissions: ["read"],
failMessage: "Secret documentation is restricted"
}
]
};
// Check if a file can be read (without user principals)
const result = evaluateAccess("/docs/readme.md", "read", policy);
if (result.allowed) {
console.log("Access granted:", result.message);
} else {
console.log("Access denied:", result.message);
}
// Check access with user principals/teams
const userPrincipals = ["developer", "admin"];
const resultWithPrincipals = evaluateAccess(
"/admin/config.json",
"write",
policy,
userPrincipals
);
const teamBasedPolicy: AclPolicy = {
entries: [
{
action: "allow",
resource: "/src/**",
permissions: ["read", "write"],
principals: ["developer", "admin"]
},
{
action: "deny",
resource: "/src/secrets/**",
permissions: ["read"],
principals: ["developer"],
failMessage: "Developers cannot access secret files"
}
]
};
// Developer trying to read source code
const devResult = evaluateAccess(
"/src/app.js",
"read",
teamBasedPolicy,
["developer"]
);
console.log(devResult.allowed); // true
// Developer trying to read secrets
const secretResult = evaluateAccess(
"/src/secrets/api-key.txt",
"read",
teamBasedPolicy,
["developer"]
);
console.log(secretResult.allowed); // false
console.log(secretResult.message); // "Developers cannot access secret files"
// Admin can access everything
const adminResult = evaluateAccess(
"/src/secrets/api-key.txt",
"read",
teamBasedPolicy,
["admin"]
);
console.log(adminResult.allowed); // true
// User with no matching principals
const viewerResult = evaluateAccess(
"/src/app.js",
"read",
teamBasedPolicy,
["viewer"]
);
console.log(viewerResult.allowed); // false
After setting up your ACL, make sure you test it:
- ACL testing and solutions to common issues: get tips on testing your policies, debugging common issues, and ensuring your ACL configuration works as expected.