Access Requests & Grants
An agent asks a storage for access with an access request; a storage controller gives it with an
access grant. A grant authorizes its assignee directly — no ACL edit — and deleting it takes the
access away again on the next request. Both follow lws10-core’s access requests and its access
profile. The services are on by default (lws.access-requests.enabled).
Discovery and endpoints
The storage description advertises an AccessRequestService and an
AccessGrantService, each with a serviceEndpoint and conformsTo the LWS access profile. Both
endpoints are LWS containers served as application/lws+json:
| Endpoint | POST (collection) |
GET / HEAD |
DELETE (entry) |
|---|---|---|---|
/.lws/access-requests |
any authenticated agent submits a request | the requester sees their own; a controller sees all | the requester or a controller |
/.lws/access-grants |
a storage controller issues a grant | an assignee sees grants naming them; a controller sees all | the issuer or a controller (revocation) |
The paths sit under lws.system-prefix (default /.lws). A POST answers 201 with Location
(the server assigns the id). Listings are container representations, paginated at
lws.container.page-size like any other container, whose members are data resources typed
["DataResource", "AccessRequest"] or ["DataResource", "AccessGrant"] with format
application/lws+json. Every read carries the storage Link header.
A storage controller is an agent with Control over the storage root: an owner in owner mode,
an agent granted acl:Control on the root ACL under WAC. A storage in open mode (no lws.owners)
has no controller, so grants cannot be issued there at all.
Issuing a grant
POST /.lws/access-grants Content-Type: application/lws+json (storage controller)
{
"@context": "https://www.w3.org/ns/lws/v1",
"type": ["AccessGrant"],
"storage": "http://localhost:8080/",
"access": [
{ "type": ["AccessPolicy"], "action": ["read"],
"assignee": "https://bob.example/me",
"target": { "type": "StorageResource", "value": ["http://localhost:8080/doc"] } }
]
}
The document is validated at creation:
storageis required and must be this storage (a trailing/is supplied if missing);accessis a non-empty array ofAccessPolicyentries, each with a non-emptyactionarray (read,modify,create,delete) and anassigneeURI;- a
targetis required (the profile makes it optional, but a grant without one would authorize nothing or everything), everytarget.valuemust be inside this storage, and an unknown matchertypeis refused; - an
inbox, if given, must be an acceptable delivery target (the same policy as webhooks).
An access request has the same shape with type ["AccessRequest"].
How grants are enforced
A grant-aware authorizer (GrantAuthorizer) is layered over the base model, owner or
WAC: an operation is permitted if the base permits it or an active grant
authorizes it.
- Actions map to operations:
read→ GET/HEAD,modify→ PUT/PATCH,create→ POST,delete→ DELETE. Grants never conferControl. - Assignee is the principal’s identifier, or
http://xmlns.com/foaf/0.1/Agentfor public access — the way to open one resource or subtree whenlws.public-readis off. - Target: each
valuematches a resource exactly or, for a container, its whole subtree. The matchertypenarrows that:StorageResource(the default) matches any resource,DataResourceandContaineronly their own kind. -
Constraints (
constraint: an array of{leftOperand, operator, rightOperand}) must all hold, and are evaluated fail-closed — an operand or operator the server does not understand makes the policy authorize nothing:leftOperandOperators Compared with dateTimegteq,lteqthe current time clienteq,isAnyOfthe credential’s client_idformateq,isAnyOfthe target’s media type ( mediaType, the pre-August-2026 name, is still read)typeeq,isAnyOfthe target’s types, including those its Link: rel="type"headers declarepurposeeq,isAnyOfthe purposes the client declares in an LWS-Purposerequest header
A grant carries only the authority of the agent who issued it, and that authority is re-checked on
every evaluation: remove the issuer from lws.owners, or revoke its acl:Control on the root, and
every grant it made stops authorizing on the next request, with no restart and nothing to clean up.
An owner rotation is therefore also a grant revocation — re-issue anything you meant to keep.
As lws10-core’s privacy considerations advise, a grant with a client constraint is not shown to its
assignee through a different client.
acl:originand the grantpurposeare client-declared, not cryptographically attested — as is the nature of origin/purpose policy. Treat them as advisory scoping. See Security.
Notifications
On creation the server delivers a signed notification — a Create activity whose object is the
new document, typed ["DataResource", "AccessGrant"] or AccessRequest, RFC 9421-signed like a
webhook — to the relevant inboxes:
- the document’s own
inbox; - for a new request, the controller inbox
lws.access-requests.controller-inbox, if set; - for a grant that references its request through a
requestlink, that request’sinbox.
See Notifications for the envelope and signatures.
Auditing
A grant appears in no owner list and no ACL, so the server logs at startup how many grants are
stored and how many are inert because their issuer no longer controls the storage. Review them at
/.lws/access-grants as a controller.