Groups
A group is a named set of principals. Name a group in an AuthorizationRule actor, and the rule applies to every member — so you grant a permission once and manage who holds it in one place.
Membership can come from a Group document you store on the host, the identity provider's roles, or the built-in groups. However it arises, it resolves to the group name used in actor, so a rule need not know where the membership came from.
The Group document
The platform-native group is an HTML Microdata item of type https://pagelove.org/Group, stored anywhere on the host. It carries a name and one member per email address. Membership is managed as data — editing the document is the whole operation.
<div itemscope itemtype="https://pagelove.org/Group">
<meta itemprop="name" content="editors">
<meta itemprop="member" content="alice@example.com">
<meta itemprop="member" content="bob@example.com">
</div>
A request whose verified email matches one of the member values is granted the group's name (editors) as an actor.
Fields
| Field | Cardinality | Description |
|---|---|---|
name |
1..1 |
The group name — the value an AuthorizationRule actor names to grant the group's members. A group with a blank name is ignored. |
member |
0..n |
A member's email address, matched exactly against the request principal's verified email. |
Verified email only
A member is matched against the principal's email only when that email is verified — the OIDC email_verified claim must be true. An unverified or absent email is never a member, so it can never inherit a group's grants. This is the same trust gate as a verified-email actor.
Custom membership: subtype Group and override includes()
Membership is decided by the group's includes(email) method. The base Group's implementation is the exact-email match over member values described above — and because it is an ordinary schema method, you can define your own group type (a schema whose parent is https://pagelove.org/Group) and override includes() to implement any membership policy Sessel can express. A subtype group participates in authorization exactly like a base Group — its name grants as an actor — with or without member entries.
For example, a group that includes everyone at an email domain:
<div itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="https://example.com/TeamGroup">
<meta itemprop="name" content="TeamGroup">
<meta itemprop="parent" content="https://pagelove.org/Group">
<div itemprop="property" itemscope itemtype="https://pagelove.org/Property">
<meta itemprop="name" content="domain">
<meta itemprop="type" content="https://schema.host/Text">
<meta itemprop="cardinality" content="1..1">
</div>
<li itemprop="property" itemscope itemtype="https://pagelove.org/Method">
<meta itemprop="name" content="includes">
<meta itemprop="returns" content="https://schema.host/Boolean">
<li itemprop="parameter" itemscope itemtype="https://pagelove.org/Parameter">
<meta itemprop="name" content="email">
<meta itemprop="type" content="https://schema.host/Text">
</li>
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/Sessel">
<script itemprop="source" type="text/sessel">email.endsWith(self.domain)</script>
</div>
</li>
</div>
Store the schema under /system/schemas/, then store instances of it like any group:
<div itemscope itemtype="https://example.com/TeamGroup">
<span itemprop="name">example-staff</span>
<meta itemprop="domain" content="@example.com">
</div>
Every request whose verified email ends in @example.com is now a member of example-staff — no member list to maintain. A subtype that does not override includes() inherits the base exact-email behaviour.
Other sources of membership
A principal can also belong to a group without a Group document:
- The OIDC
rolesclaim — any role carried in the principal's session (asserted by the identity provider) is usable directly as a group name in anactor. - The built-in groups
usersandauthenticated— both match any signed-in principal, regardless of roles orGroupdocuments. They differ from*, which also matches anonymous requests.
So actor editors matches whether that membership came from a Group document, an OIDC role, or was named directly.
How a group actor is matched
A group name sits at the same specificity tier as a role or a verified email — below an exact user name, above the wildcard *. The usual allow-list pattern therefore composes: a blanket * … Deny alongside a specific editors … Allow grants the group and no one else. See Conflict resolution for how overlapping rules settle.
<div itemscope itemtype="https://pagelove.org/AuthorizationRule">
<meta itemprop="actor" content="editors">
<meta itemprop="resource" content="/blog/*">
<meta itemprop="method" content="POST">
<meta itemprop="method" content="PUT">
<meta itemprop="method" content="DELETE">
<meta itemprop="action" content="Allow">
</div>
Membership beyond verified email
The built-in Group matches on verified email only. When you need membership keyed on something else — internal user ids, a service principal, a directory lookup — keep that membership in your own documents and expand it with a schema @read resolver on AuthorizationRule's actor property. The Group-based permissions recipe walks through it.
See also
- Authorization rules — name a group in a rule's
actor; the matching model and conflict resolution. - Group-based permissions — a worked how-to for granting a permission to a group.