Multi-Role Rules
Problem
You have multiple roles that share identical permissions on a resource. Writing one rule per role creates repetition and means you must update multiple rules when permissions change.
Solution
Use an array for role. A rule with role: ['viewer', 'editor'] matches any principal that holds at least one of those roles.
ts
import { ANONYMOUS, createWard } from '@vielzeug/ward';
const ward = createWard<'read' | 'update' | 'delete'>([
// One rule instead of three separate allow rules
{ role: ['viewer', 'editor', 'admin'], resource: 'posts', action: 'read', effect: 'allow' },
{ role: ['editor', 'admin'], resource: 'posts', action: 'update', effect: 'allow' },
{ role: 'admin', resource: 'posts', action: 'delete', effect: 'allow' },
]);
ward.can({ id: 'u1', roles: ['viewer'] }, 'posts', 'read'); // true
ward.can({ id: 'u1', roles: ['viewer'] }, 'posts', 'update'); // false
ward.can({ id: 'u2', roles: ['editor'] }, 'posts', 'update'); // true
ward.can({ id: 'u3', roles: ['admin'] }, 'posts', 'delete'); // trueWith ANONYMOUS in a Multi-Role Array
ANONYMOUS is valid inside a multi-role array. The rule matches both unauthenticated visitors and any authenticated role listed alongside it.
ts
import { ANONYMOUS, createWard } from '@vielzeug/ward';
const ward = createWard([{ role: [ANONYMOUS, 'viewer'], resource: 'posts', action: 'read', effect: 'allow' }]);
ward.can(null, 'posts', 'read'); // true — anonymous
ward.can({ id: 'u1', roles: ['viewer'] }, 'posts', 'read'); // true
ward.can({ id: 'u2', roles: ['editor'] }, 'posts', 'read'); // false — editor not listedWith Priority and WILDCARD
A multi-role rule scores as specific (score 1) unless the array contains WILDCARD. At equal priority, a multi-role allow beats a WILDCARD deny.
ts
import { WILDCARD, createWard } from '@vielzeug/ward';
const ward = createWard([
{ role: WILDCARD, resource: 'posts', action: 'read', effect: 'deny', priority: 0 },
{ role: ['viewer', 'editor'], resource: 'posts', action: 'read', effect: 'allow', priority: 0 },
]);
ward.can({ id: 'u1', roles: ['viewer'] }, 'posts', 'read'); // true — specific rule wins
ward.can({ id: 'u2', roles: ['guest'] }, 'posts', 'read'); // false — wildcard deny appliesPitfalls
- An authenticated principal is never matched by
ANONYMOUSalone. If you want a rule to apply to all users (anonymous and authenticated), combineANONYMOUSwithWILDCARDis not supported — use two separate rules instead. role: []is invalid and throws atcreateWard()time. Always supply at least one role string.