Portable Web Spaces Authorization Profile v0.1

Unofficial Draft

More details about this document
Latest published version:
https://w3c-ccg.github.io/wallet-attached-storage-spec/authz-profile/
Latest editor's draft:
https://w3c-ccg.github.io/portable-web-spaces-spec/
History:
Commit history
Editor:
Dmitri Zagidulin
Feedback:
GitHub w3c-ccg/portable-web-spaces-spec (pull requests, new issue, open issues)

Abstract

The baseline authorization profile for Portable Web Spaces: object capabilities (zCaps) invoked over HTTP Signatures, rooted in a Space's controller DID, together with the PublicCanRead access control policy.

Status of This Document

This document is a draft of a potential specification. It has no official standing of any kind and does not represent the support or consensus of any standards organization.

This is an experimental specification and is undergoing regular revisions. It is a companion to the Portable Web Spaces specification, which defines the storage API this profile authorizes.

1. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MAY, MUST, MUST NOT, and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

2. Introduction

Portable Web Spaces (PWS) [PWS] is a permissioned storage API. Its core specification defines the contract every authorization profile meets (private by default, the Space controller as the root of trust, and "not found" in place of "not authorized") and the socket a profile plugs into (the /policy auxiliary resource). It leaves the mechanism to an authorization profile. This document is that profile for the baseline: it defines how a caller proves it may act on a PWS target, and the one access control policy type that lets a controller open a target to callers in general.

Like many authorization specifications, this profile tries to address opposing tensions. On the one hand, to cover the full range of use cases, it needs to be delegatable, revocable, secure, flexible, and thus capability-based. On the other hand, for ease of implementation and adoption, and for maximum developer usability, the profile must make the most common operations as simple and friction-free as possible.

To that end, the profile offers the following layered mechanisms.

  1. Root Access: For basic admin CRUD operations, use the Space's controller DID directly to sign API calls with HTTP Signatures.
  2. Public Read: For the common "public read" use case (the typical web publishing workflow, where a site or a file is shared for anyone to access via an HTTP GET), use the simple { "type": "PublicCanRead" } policy, see 10.1 PublicCanRead.
  3. Advanced Delegatable Capabilities ("anyone with the link..." style): Use zCaps [ZCAP], see 8.2 Delegation.
  4. Policy Based Access Control (including the familiar "share with this list of people or groups" style): Use the Space's linkset property to point to a linkset that includes a URL to an access control policy document. This version of the profile defines one policy type; the socket is defined by [PWS].

The [ZCAP-GUIDE] is the explanatory companion to this profile. It walks through how capabilities are delegated, invoked, and verified, and how a root of trust is established. This document states the requirements; the guide explains them.

2.1 Relationship to the Core Specification

The core specification [PWS] names this profile as the baseline: every server conformant to its Minimal profile implements this document. A server MAY implement further authorization profiles in addition; see the Authorization section of [PWS].

This profile's persistent identifier is https://w3id.org/pws/authz-profile. A server that implements it lists the identifier in its service description, see 9. Service Description Entry.

The following are defined by [PWS] and only cited here:

2.2 Reading This Document

Note
This subsection is non-normative.

All examples use the Space id 81246131-69a4-45ab-9bff-9c946b59cf2e on the host example.com, the same Space every example in [PWS] uses. Paths are written from the server root, which may be a subpath of an origin.

Request examples in [PWS] abbreviate the credential as a placeholder, Authorization: .... In every case it stands for a signed capability invocation constructed as described in 6. Performing Authorized API Calls. The fully expanded form appears once, in the worked example of 7. Request Body Integrity (Digest Header).

3. Terminology

action (allowedAction)
The kind of operation a request performs on a target, named by a capability so it can be authorized. This profile uses the uppercase HTTP method names (GET, POST, PUT, DELETE) as its action vocabulary. See 8. Capability Invocation.
capability invocation
A request that presents a capability and is signed by a key the capability authorizes. See 6. Performing Authorized API Calls.
controller
An entity that can make changes to a given object. A Space's controller is a DID, see 5. Space controller and the Root of Trust.
root capability
The implied capability for a target whose controller is the Space's controller; it is the root of trust from which all other capabilities for that target are delegated. See 8.1 Root Capability.
target (invocationTarget)
The resource a request acts on, as the full request URL (scheme, host, port, and path), and the scope a capability authorizes. A capability's invocationTarget MUST match the request target for the invocation to be valid, see 8. Capability Invocation.
zCap (Authorization Capability)
A delegatable, attenuable object capability as defined by [ZCAP]: a document naming an invocationTarget, the allowedActions it permits, and the controller who may invoke or delegate it. See the [ZCAP-GUIDE] for an introduction.

4. Specification Dependencies at a Glance

This profile uses the following specifications.

  1. Identity (for controllers or clients/agents): DID 1.0
  2. Capability data model: [ZCAP]
  3. Protocol for getting authorization: Out of scope (implementers are encouraged to use VC-API, OpenId4VP, OAuth2, or GNAP, as appropriate)
  4. Proof of Possession / authorization invocation: HTTP Signatures. MUST - [HTTP-SIGNATURES] (Cavage draft 12), MAY - [RFC9421] HTTP Message Signatures (a future direction for this profile, see the note below)
  5. Request body integrity: the Digest header, bound to the request signature -- see 7. Request Body Integrity (Digest Header)
  6. Access Control / Policy language data model: the /policy socket of [PWS], with the policy type this profile defines in 10. Policies
Editor's note
**Signature suite (transitional).** The signature suite of this profile is the Cavage HTTP Signatures draft (draft 12). The covered-headers list uses its pseudo-headers ((key-id), (created), (expires), (request-target)), and the worked examples carry an Authorization: Signature ... header in its syntax. This matches every current implementation. [RFC9421] HTTP Message Signatures (the Signature and Signature-Input headers) is a future direction for this profile. It will be adopted as a coordinated migration across the implementations, together with the Content-Digest migration described in 7. Request Body Integrity (Digest Header).

5. Space controller and the Root of Trust

Conceptually, the Space's controller serves as the root of trust and authorization for any operations on the Space or its Collections or Resources. That is, any operation requiring an authorization MUST provide a chain of proof all the way to the Space controller, by one of the following:

  1. Direct: Provide a root capability invoked directly by the controller, or
  2. Delegated: Invoke a capability delegated to some other agent by the controller (see 8.2 Delegation), or
  3. Matching Policy: Match an access control policy at the target or inherited from a container above it (see 10. Policies). A policy is related to the Space controller because it can only be written by the controller or by a party the controller delegated to.

The form a controller takes (a DID), the DID methods a server accepts, and the rules for setting or changing a controller are defined by [PWS] in its Space controller and the Root of Trust section and its Space Controller DID Method Registry. Every server implementing this profile supports did:key [DID-KEY] controllers with Ed25519 keys in the Multikey encoding of [CID], as that registry requires.

5.1 Current-key-set rule

Whatever the DID method, the key material that verifies an invocation or a delegation is the signer's DID document as resolved at the time of verification. A signature verifies if and only if its verification method is present in that document, under the verification relationship the operation requires: capabilityInvocation for an invocation, capabilityDelegation for a delegation. A did:key document never changes. A document of a method with a mutable, verifiable history (see the verified-log DID methods of [PWS]) changes as that history is extended, so the set of accepted keys is the current one. A delegation signed by a key that has since been removed from the delegator's document stops verifying the moment that verification method leaves the document, even though the capability itself was not revoked. Every capability delegated onward from it stops verifying with it. Removing a key from a controller's document is therefore an immediate, server-enforced withdrawal of everything that key delegated, independent of any capability revocation mechanism.

This rule applies to every DID that signs in a capability chain, not only to the Space's controller. A capability rooted in a did:key-controlled Space MAY be delegated to a DID of any method the server supports; the server resolves that delegate's document the same way when it verifies the delegate's invocation or onward delegation.

5.2 Authorizing Space creation

When a Space is created via an HTTP POST or PUT operation (see the Create Space and Update (or Create by Id) Space operations of [PWS]), the controller for that Space is set explicitly. That is, a client specifies the controller as part of the payload of the PUT or POST create Space request, and the server MUST verify that the invocation is authorized by that controller, by one of the first two mechanisms above: either directly -- the signing key (key ID) used in the headers is authorized in the capabilityInvocation section of the controller's DID document -- or via a capability delegated by the controller to the signing DID. (The third mechanism, matching policy, does not apply: no Space, and therefore no policy, exists yet.) An invocation that is not authorized by the body's controller is refused with controller-mismatch (400), see 11. Errors.

6. Performing Authorized API Calls

Unless otherwise explicitly allowed via access control policy (see 10. Policies), all PWS API calls require authorization.

This can be done in one of two ways:

  1. (for admin-like root access) Use the controller DID directly to sign HTTP API requests using the HTTP Signatures specification, invoking the target's root capability.
  2. (for advanced delegatable use cases) Use HTTP Signatures in combination with [ZCAP], and include a capability invocation header in the API request.

In both cases the request carries a Capability-Invocation header naming the capability being invoked and the action performed, and an Authorization: Signature ... header carrying an HTTP Signature [HTTP-SIGNATURES] over the request. The worked example in 7. Request Body Integrity (Digest Header) shows both headers in full.

7. Request Body Integrity (Digest Header)

When an authorized request carries a body (a Resource write, a Space create, and so on), this profile binds the body to the request's HTTP Signature so that the payload cannot be substituted without invalidating the signature:

  1. The client MUST include a Digest header whose value is the hash of the request body, carried as a mh (multihash) parameter: a multibase base64url-encoded (u prefix) multihash of the body's SHA-256 digest (Multihash and Multibase as defined in [CID]). For example:

    Digest: mh=uEiCPO-qYr-z0GYV5F75-N1l8Rhjv4xIkKZsnbTZeZ7emSA
    
  2. The content-type and digest headers MUST be included in the signature's covered (signed) headers list, alongside the Cavage draft-12 pseudo-headers (key-id), (created), (expires), and (request-target), and the host and capability-invocation headers.

  3. For any request that carries a Content-Type header, the server MUST require digest among the covered headers, and SHOULD independently recompute the digest of the received body and compare it to the Digest header value. A missing, malformed, or non-matching Digest on a request with a body is rejected with an invalid-authorization-header (400) error, see 11. Errors.

Bodyless requests (GET, HEAD, DELETE) carry no Digest header.

The requirement applies per request. A chunk write (the chunk endpoints defined by [PWS-EC]) therefore carries a Digest of that chunk, and a query request (see the Query Profile Registry) carries a Digest of the query body it signs.

Example authorized write request, showing the Digest header and the covered headers list (the Space's controller invoking the root capability for the target; line breaks within the Authorization header are for display only):

PUT /space/81246131-69a4-45ab-9bff-9c946b59cf2e/photos/sunset.png HTTP/1.1
Host: example.com
Content-Type: image/png
Digest: mh=uEiCPO-qYr-z0GYV5F75-N1l8Rhjv4xIkKZsnbTZeZ7emSA
Capability-Invocation: zcap id="urn:zcap:root:https%3A%2F%2Fexample.com%2Fspace%2F81246131-69a4-45ab-9bff-9c946b59cf2e%2Fphotos%2Fsunset.png",action="PUT"
Authorization: Signature keyId="did:key:z6MkpBMbMaRSv5nsgifRAwEKvHHoiKDMhiAHShTFNmkJNdVW#z6MkpBMbMaRSv5nsgifRAwEKvHHoiKDMhiAHShTFNmkJNdVW",
  headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",
  signature="6GoRQ+rW69wBhNyERkafAXEZXZArezHvGRNUWC0HNI4Ss1xAiiMHdayS5aA2R6hLuYRNw6h9J9eCmQVMuHE1Bw==",
  created="1758150502",expires="1758151102"

...binary PNG bytes...
Editor's note
**Digest vs Content-Digest.** The Digest header used by this profile descends from [RFC3230] (Instance Digests in HTTP). [RFC9530] (Digest Fields) obsoletes RFC 3230 and replaces Digest with Content-Digest / Repr-Digest. The current PWS implementation stack uses the legacy header with a multihash value; migration to Content-Digest (alongside the move to [RFC9421] HTTP Message Signatures, see 4. Specification Dependencies at a Glance) is a future direction for this profile.

8. Capability Invocation

A capability invocation names an action that the invoked capability must permit. This profile uses the uppercase HTTP method names as its action vocabulary:

The target of a request is its full request URL: scheme, host, port, and path. A target covers everything beneath it (see 8.2 Delegation). Query parameters select within a target and do not change it, so a capability for a container authorizes every page of its listing.

A request is authorized by a capability when all the following hold:

  1. the capability's invocationTarget matches the request's target;
  2. the capability's allowedAction includes the request's action (the HTTP method); and
  3. the invocation is signed by a key the capability authorizes, carried as a valid HTTP Signature over the request (see 6. Performing Authorized API Calls), and that key is present in the signer's DID document as resolved at verification time (see 5.1 Current-key-set rule).

8.1 Root Capability

Every target has an implied root capability whose controller is the Space's controller. It is identified by the URI urn:zcap:root: followed by the percent-encoded target URL:

{
  "@context": "https://w3id.org/zcap/v1",
  "id": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fspace%2F81246131-69a4-45ab-9bff-9c946b59cf2e%2Fmessages%2Fhello-world",
  "invocationTarget": "https://example.com/space/81246131-69a4-45ab-9bff-9c946b59cf2e/messages/hello-world",
  "controller": "did:key:z6MkpBMbMaRSv5nsgifRAwEKvHHoiKDMhiAHShTFNmkJNdVW"
}

The Space controller MAY invoke the root capability directly -- signing the request with a key listed in the capabilityInvocation section of the controller's DID document -- to perform any operation. This is the "root access" path. All other authorized access derives from a capability delegated, directly or transitively, from this root.

8.2 Delegation

To grant another agent access, the controller (or any agent holding a sufficiently broad capability) delegates a capability that names the grantee as its new controller, the invocationTarget to scope it to, and the allowedActions to permit. A delegation MAY set an expires time. For example, granting another DID read-only access to a single Collection:

{
  "@context": "https://w3id.org/zcap/v1",
  "id": "urn:uuid:6c9f3a1e-2b4d-4f8a-9c1e-7d2b3a4c5e6f",
  "parentCapability": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fspace%2F81246131-69a4-45ab-9bff-9c946b59cf2e%2Fmessages%2F",
  "invocationTarget": "https://example.com/space/81246131-69a4-45ab-9bff-9c946b59cf2e/messages/",
  "controller": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "allowedAction": ["GET"],
  "expires": "2026-12-31T23:59:59Z",
  "proof": { "...": "delegation proof signed by the parent capability's controller" }
}

The delegated capability is handed to the recipient out of band. The recipient invokes it by signing a request with their own key and including the capability in the Capability-Invocation header.

A target covers everything beneath it, so the choice of target is what attenuates a grant. A capability on a Collection URL, as above, covers the Collection's listing, every Resource in it, and its Metadata object -- which is what "share this collection" means. To grant the Collection's metadata alone, without its Resources, target the meta URL instead; that grant also covers the governing history log beneath it (see the Collection Governing History Log of [PWS]). The same holds one level up for a Space.

Editor's note
**Revocation.** This profile does not yet define a revocation operation, although the goals of [PWS] require that a grant can be withdrawn before it expires. The current PWS implementation stack ships a Space-scoped revocation endpoint (POST /space/{space_id}/zcaps/revocations/{revocation_id}; every Space-rooted capability verification checks the presented delegation chain against the recorded revocations). A future revision will specify the operation and reserve its path segments.

9. Service Description Entry

A server that implements this profile lists it in its service description (see the Service Description of [PWS]) under the key https://w3id.org/pws/authz-profile. A version entry under that key carries, in addition to the version and url members every entry has:

The entry advertises no policy types. Listing this profile means the server evaluates every policy type this profile defines (see 10. Policies).

{
  "url": "https://example.com/pws/service",
  "specs": {
    "https://w3id.org/pws": [
      { "version": "0.5", "spaces": "https://example.com/pws/spaces/" }
    ],
    "https://w3id.org/pws/authz-profile": [
      {
        "version": "0.1",
        "url": "https://w3c-ccg.github.io/wallet-attached-storage-spec/authz-profile/",
        "signatureAlgorithms": ["EdDSA"],
        "zcapCryptosuites": ["eddsa-jcs-2022"]
      }
    ]
  }
}

A client that speaks this profile checks for the entry before its first signed request, as part of the version selection [PWS] defines. A server that lists no authorization profile the client implements is incompatible.

Editor's note
This profile does not yet catalogue which values of signatureAlgorithms and zcapCryptosuites a conformant server MUST accept. The example carries the values the reference implementations exchange today: EdDSA over Ed25519 did:key keys, and eddsa-jcs-2022 delegation proofs.

10. Policies

[PWS] stores an access control policy, a JSON document with a required type property, at the /policy auxiliary resource of a Space, Collection, or Resource, and defines how a policy is evaluated: a policy is consulted only after any capability the request carries, it can only broaden access, an absent or unrecognized type grants nothing, the most specific level wins, and the request's action is reduced to a read or write access kind. See its Access Control Policies section for the contract.

Policy type values are defined by profiles and registered in the Policy Type Registry of [PWS]. This profile defines one type. A server implementing this profile MUST evaluate it.

10.1 PublicCanRead

{ "type": "PublicCanRead" }

It grants the read access kind to any caller (including unauthenticated ones) and grants no write access. This is the canonical "public read" pattern -- for example, hosting an HTML file or an image that anyone may GET, while writes still require a capability. Setting it on a Space makes the whole Space public-readable (subject to any more specific Collection or Resource policy); setting it on a single Resource exposes only that Resource.

11. Errors

This profile registers no error kinds of its own. It reports failures with the kinds of the Error Type Registry of [PWS], as [RFC9457] problem responses:

The maximum-privacy rule of [PWS]'s Error Handling governs which of these a server may return. A credential that is present but invalid describes the request, and MAY be reported precisely. A well-formed request that simply lacks privilege for an existing target MUST be reported as not-found, indistinguishable from the target being absent, and a request that carries no credential at all MUST NOT learn from the response whether the target exists.

A. References

A.1 Normative references

[CID]
Controlled Identifiers v1.0. W3C. URL: https://www.w3.org/TR/cid-1.0/
[DID-KEY]
The did:key Method. W3C Credentials Community Group. URL: https://w3c-ccg.github.io/did-key-spec/
[HTTP-SIGNATURES]
Signing HTTP Messages (draft-cavage-http-signatures-12). M. Cavage; M. Sporny. IETF (Internet-Draft). URL: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12
[PWS]
Portable Web Spaces. Dmitri Zagidulin. W3C Credentials Community Group. URL: https://w3c-ccg.github.io/wallet-attached-storage-spec/
[PWS-EC]
Encrypted Collections for Portable Web Spaces. Dmitri Zagidulin. W3C Credentials Community Group. URL: https://w3c-ccg.github.io/wallet-attached-storage-spec/ec/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc2119/
[RFC7518]
JSON Web Algorithms (JWA). M. Jones. IETF. May 2015. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc7518/
[RFC8037]
CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE). I. Liusvaara. IETF. January 2017. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc8037/
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc8174/
[RFC9421]
HTTP Message Signatures. A. Backman, Ed.; J. Richer, Ed.; M. Sporny. IETF. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9421/
[RFC9457]
Problem Details for HTTP APIs. M. Nottingham; E. Wilde; S. Dalal. IETF. July 2023. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9457/
[ZCAP]
Authorization Capabilities for Linked Data v0.3. W3C Credentials Community Group. URL: https://w3c-ccg.github.io/zcap-spec/
[ZCAP-GUIDE]
zCap Developer Guide. Dmitri Zagidulin. URL: https://interop-alliance.github.io/zcap-developer-guide/

A.2 Informative references

[RFC3230]
Instance Digests in HTTP. J. Mogul; A. Van Hoff. IETF. January 2002. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc3230/
[RFC9530]
Digest Fields. R. Polli; L. Pardue. IETF. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9530/