Skip to main content

Team & Group Access

Managing access for individual users can quickly become overwhelming. Instead of assigning permissions user-by-user, you can group users into teams, departments, or roles, and grant permissions to the entire group.

This guide shows you how to define a group relation, add users to groups, and check if members inherit access.

Theory & Concepts

To learn more about how subjects, objects, and relationships work under the hood, read the Relations and Actions guide.

1. Configure the schema​

To support groups, your schema must include at least one relation with type: "group". This relation tells polizy that a subject is a member of another subject (which acts as a group).

import { defineSchema } from "polizy";

const schema = defineSchema({
subjectTypes: ["user", "team"],
objectTypes: ["document", "team"],

relations: {
// Declares that subjects can belong to teams
member: { type: "group" },

// Standard direct relations
owner: { type: "direct" },
viewer: { type: "direct" },
},

actionToRelations: {
view: ["viewer", "owner", "member"],
edit: ["owner"],
},
});

For more options, check out the Schema Reference.

2. Add members to a group​

Use addMember to add a user (or another subject) to a group.

import { AuthSystem, InMemoryStorageAdapter } from "polizy";

const authz = new AuthSystem({
storage: new InMemoryStorageAdapter(),
schema,
});

// Add Alice to the engineering team
await authz.addMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
});

3. Grant access to the group​

In polizy, groups are subjects. You can grant access to a group using allow, just like you would for an individual user:

await authz.allow({
who: { type: "team", id: "engineering" },
toBe: "viewer",
onWhat: { type: "document", id: "design-doc" },
});

4. Check inherited access​

When you run check for a user, polizy automatically traverses group memberships to see if they inherit the permission:

const canView = await authz.check({
who: { type: "user", id: "alice" },
canThey: "view",
onWhat: { type: "document", id: "design-doc" },
});

console.log(canView); // true (inherited through engineering team membership)

5. Remove members from a group​

When a user leaves the group, use removeMember to revoke their membership. They will immediately lose all inherited permissions:

await authz.removeMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
});

const canViewAfterLeaving = await authz.check({
who: { type: "user", id: "alice" },
canThey: "view",
onWhat: { type: "document", id: "design-doc" },
});

console.log(canViewAfterLeaving); // false

Advanced Scenarios​

Nested groups (teams within teams)​

Polizy automatically traverses nested memberships. If the frontend team is a member of the engineering team, members of frontend will inherit anything granted to engineering:

// Add frontend team as a member of the engineering team
await authz.addMember({
member: { type: "team", id: "frontend" },
group: { type: "team", id: "engineering" },
});

// Add Bob to the frontend team
await authz.addMember({
member: { type: "user", id: "bob" },
group: { type: "team", id: "frontend" },
});

// Bob inherits viewer access on the design-doc granted to engineering
const bobCanView = await authz.check({
who: { type: "user", id: "bob" },
canThey: "view",
onWhat: { type: "document", id: "design-doc" },
});

console.log(bobCanView); // true

Multiple group relations (using the as parameter)​

If your schema defines more than one group relation (for example, member and manager), you must explicitly specify which relation to use with the as parameter:

// Schema with multiple group relations
const multiGroupSchema = defineSchema({
subjectTypes: ["user", "team"],
objectTypes: ["document", "team"],
relations: {
member: { type: "group" },
manager: { type: "group" },
},
actionToRelations: {
view: ["member", "manager"],
}
});

// ... instantiate authz ...

// Specify "as" because the system cannot infer which relation to use
await authz.addMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
as: "manager",
});

// The same requirement applies when removing a member
await authz.removeMember({
member: { type: "user", id: "alice" },
group: { type: "team", id: "engineering" },
as: "manager",
});
Inference Behavior

The as parameter is optional and inferred automatically only when there is exactly one group relation defined in your schema.