The W3 protocol governs user interactions within self-certified Public Key Infrastructure (PKI)-based namespaces. Access to these namespaces, for simplicity referred to as spaces, is authorized through delegated capabilities in UCAN format.
Here we define the protocol for delivering authorized delegations to their audience.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119.
Space access is represented as a signed authorization in UCAN format. It is not enough to issue the UCAN authorization, the issuer needs some channel to deliver it to an audience.
We propose a protocol where an implementation can act as a delivery channel.
At the very high level signed authorization is a message addressed to a specific recipient (audience). The implementer of this protocol represents a message channel allowing the sender (issuer) to send a message and allowing the recipient (audience) to receive messages sent to them.
Alice has set up a space for sharing photos with her family and friends. She wants to authorize her partner Bob with write access so he can also upload photos. She wants to authorize her less tech savvy parent Mallory with just read access so she can look at photos but not add or delete them.
In this scenario Alice delegates
upload/add
capability to Bob andupload/list
capability to Mallory. The application used by Alice leverages the access protocol to send issued delegations to Bob and Mallory. Applications used by Bob and Mallory leverage the access protocol to receive messages sent to them, transparently gaining access to the space that Alice has shared access to.
Alice has created a new space for storing photos on her laptop and uploaded some photos. Later she picks up her phone and logs in with her account to upload some photos to her space.
In this scenario after the space is created the access protocol is used to delegate full authority over to Alice's account. Later, when Alice logs in on her phone her account receives delegated capabilities over the access protocol, thereby gaining access to the space.
UCANs MUST be encoded with an IPLD codec. DAG-CBOR is RECOMMENDED.
Name | Description |
---|---|
Principal | The general class of entities that interact with with a UCAN. Listed in the iss or aud field |
Issuer | Principal sharing access. It is the signer of the UCAN. Listed in the iss field |
Audience | Principal access is shared with. Listed in the aud field |
Namespace or space for short is an owned resource that can be shared. It corresponds to the asymmetric keypair and is identified by the did:key
URI.
A space is always listed in the with
field of the UCAN capability.
The owner of the space is the holder of its private key. The space owner can share limited or full access to their space via a UCAN delegation issued by the space did:key
and signed with the space's private key.
Access is defined in terms of UCAN delegation, where the level of the access is denoted by the set of capabilities delegated.
Example in DAG-JSON
Space owner authorizes did:key:zBob
(aud
field) by delegating store/*
capabilities (can
field) for did:key:zSpace
space (iss
field).
{
"v": "0.9.1",
"iss": "did:key:zSpace",
"aud": "did:key:zBob",
"att": [
{
"can": "store/*"
"with": "did:key:zSpace",
}
],
"prf": [],
"fct": [],
"exp": 1740357624,
"s": {"/": {"bytes": "7aED...A0"}},
}
type Access union {
| AccessDelegate "access/delegate"
| AccessClaim "access/claim"
| AccessRequest "access/request"
} representation inline {
discriminantKey "can"
}
The access/delegate
capability MAY be invoked by an authorized agent to send a set of delegations to their respective audiences.
The following example illustrates did:key:zAlice
invoking access/delegate
capability with web3.storage, requesting it to send a delegation from access example to their audience.
{
"v": "0.9.1",
"iss": "did:key:zAlice",
"aud": "did:web:web3.storage",
"att": [
{
"with": "did:key:zAlice",
"can": "access/delegate",
"nb": {
"delegations": { "bafy...prf1": { "/": "bafy...prf1" } }
}
}
],
"prf": [],
"exp": 1705622469,
"s": {"/": {"bytes": "7aED...A0"}},
}
type AccessDelegate struct {
with AgentDID
nb Delegate
}
type Delegate struct {
delegations { String: &UCAN }
}
type AgentDID = string
The with
field MUST be set to the did:key
identifying the space where delegations will be stored.
An implementation MAY deny a request if the specified space has no capacity to store the supplied UCANs, or if the space does not have the access/delegate
capability provisioned.
The protocol intentionally does not prescribe how to transfer linked UCANs leaving it up to implementations.
⁂ w3up implementation REQUIRES all linked UCANs be bundled in the invocation.
Please note that all the following DIDs could be different from one another
- Issuer of the linked delegations (in the
nb.delegation
) - Issuer of the invocation.
- Space where delgations will be stored
In other words, a delegation MAY be sent by anyone, it does not have to be the issuer of the delegation. Sent delegation MAY be delegating capability to a resource different from the space where sent delegation will be stored.
This field MUST be set to a set of UCAN IPLD Links represented by an IPLD Map. Map keys SHOULD be their corresponding values encoded as strings. It is RECOMMENDED to use base32 encoding.
The protocol intentionally does not specify how to transfer linked UCANs leaving it up to the implementations to decide.
⁂ w3up implementation REQUIRES that all linked UCANs be bundled with the invocation.
The implementation MUST respond with UCAN Receipt. On success result MUST be a Unit
(empty IPLD Map) type.
On failure, the result MUST have a message
string field describing the error.
The access/claim
capability MAY be invoked by an authorized agent to receive the
capabilities that were delegated to the audience corresponding to the with
field.
The following example illustrates did:key:zBob
invoking access/claim
capability with web3.storage requesting delegations where did:key:zBob
is an audience like one in the access example.
{
"v": "0.9.1",
"iss": "did:key:zBob",
"aud": "did:web:web3.storage",
"att": [
{
"with": "did:key:zBob",
"can": "access/claim",
}
],
"prf": [],
"exp": 1705622469,
"s": {"/": {"bytes": "7aED...A0"}},
}
type AccessClaim struct {
with DID
}
type DID = string
The with
field MUST be set to the DID identifying the audience of the
delegations.
The implementation MUST respond with a UCAN Receipt. On success, the result MUST include the set of UCAN IPLD Links represented as an IPLD Map where keys SHOULD be corresponding values encoded as strings. It is RECOMMENDED to use base32 encoding for the keys.
The protocol intentionally does not specify how to transfer linked UCANs leaving it up to implementations.
⚠️ w3up implementation currently is incompatible as it sends binary encoded UCANs as values of the map as opposed to links.
The access/request
capability MAY be invoked by a user agent / application to get access to the required capabilities.
⚠️ w3up implementation currently does not support this capability.
User [agent] did:key:z6Mkk...xALi
(with
field) is requesting authorization from [email protected]
account (aud
field) on a space with following capabilities:
store/add
wheresize
is greater or equal than1024
.store/list
store/get
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:mailto:web.mail:alice",
"att": [
{
"with": "did:key:z6Mkk...xALi",
"can": "access/request",
"nb": {
"can": {
"store/add": [
{">=": {"size": 1024}}
],
"store/list": [],
"store/get": []
},
}
}
],
"prf": [],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJbJqmyMxTxcK05XQKWfvxG+Tv+LWCJeE18RSMnciCZ/RQ21U75LA0uFSvIjdqnF5RaauZTE8mh2ZYMBBejdJQ4"
}
}
}
type AccessRequest struct {
can { Ability: [Clause] }
}
type Ability = string
type Clause union {
# Logic combinators
"not": Clause
"or": Clause
"and": Clause
# Predicates
">": CompareClause
"<": CompareClause
">=": CompareClause
"<=" CompareClause
"=" CompareClause
"!=" CompareClause
"like" LikeClause
} representation keyed
type CompareClause = { Attribute: Compare }
type Compare union {
| CompareClause map
| Float float
| Int int
| String string
| Bytes bytes
| Link link
} representation kinded
type LikeClause { Attribute: Like }
type Like union {
| LikePattern string
| LikeClause map
} representation kinded
# Similar to SQLite like pattern
# - Any except for "%" and "_" characters matches itself.
# - An underscore "_" matches any single character.
# - A percent "%" symbol matches any single character.
type LikePattern = string
The resource (with
field) MUST be set to the DID of the principal requesting an authorization.
ℹ️ It should be noted that the resource specified in the
with
field of the authorization request is NOT REQUIRED to be the same as the issuer of the request.
The [audience] (aud
field) MUST be set to the account DID from which the authorization is being requested.
ℹ️ Implementations MAY choose to support requests from non account principals if they choose so.
The nb.can
field MUST be an IPLD Map describing requested capabilities. Keys of nb.can
map MUST be abilities requested. Values of the nb.can
map MUST be constraints that capability corresponding to the key SHOULD satisfy. All requested capabilities SHOULD be for the same resource space.
⚠️ Has been deprecated in favor of access request
The access/authorize
capability MAY be invoked by a user agent / application to get access to the required capabilities.
{
"v": "0.9.1",
"iss": "did:key:z6Mkk...xALi",
"aud": "did:mailto:web.mail:alice",
"att": [
{
"with": "did:key:z6Mkk...xALi",
"can": "access/request",
"nb": {
"iss": "did:mailto:web.mail:alice",
"att": [{"can": "store/add"}]
}
}
],
"prf": [],
"exp": 1685602800,
"s": {
"/": {
"bytes": "7aEDQJb...dJQ4"
}
}
}
type AccessAuthorize struct {
iss DID
att [CapabilityRequest]
}
type CapabilityRequest struct {
can Ability
}
type Ability = string
The resource (with
field) MUST be set to the DID of the principal requesting an authorization.
The [nb.iss
] MUST be set to the account DID from which the authorization is being requested.
The nb.att
field MUST be an array of objects. Each object in nb.att
field MUST
have the can
field set to the requested ability. All requested abilities SHOULD be for
the same resource space.
Alice installs the w3up
program and runs it the first time. The program asks what email address to use for the account. Alice types [email protected]
. The program derives a did:mailto:web.mail:alice
DID and requests authorization from it. Next the program invokes an access claim capability and discovers that the account has no space so it creates a new keypair and a corresponding space did:key:zAliceSpace
, provisions it and after confirmation from Alice sets up space recovery by delegating full authority to Alice's account:
{
"v": "0.9.1",
"iss": "did:key:zAliceSpace",
"aud": "did:mailto:web.mail:alice",
"att": [
{
"with": "did:key:zAliceSpace",
"can": "*"
}
],
"prf": [],
"exp": null,
"s": {
"/": {
"bytes": "7aEDQJb...dJQ4"
}
}
}
The program invokes an access delegate capability with the account delegation so it can be received anywhere that Alice logs in with her account.
{
"v": "0.9.1",
"iss": "did:key:zAliceSpace",
"aud": "did:web:web3.storage",
"att": [
{
"with": "did:key:zAliceSpace",
"can": "access/delegate",
"nb": {
// Map of delegation links to be stored for their audiences.
"delegations": { "bafy...prf1": { "/": "bafy...prf1" } }
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": null,
"s": {
"/": {
"bytes": "7aEDQJb...dJQ4"
}
}
}
When Alice runs the w3up
program on her other device, and logs in to her account, the program invokes an access authorize capability to get access on this device.
{
"v": "0.9.1",
"iss": "did:key:zAli",
"aud": "did:web:web3.storage",
"att": [
{
"with": "did:key:zAli",
"can": "access/authorize",
"nb": {
"iss": "did:mailto:web.mail:alice",
"att": [{"can": "*"}]
}
}
],
"prf": [],
"exp": null,
"s": {
"/": {
"bytes": "7aEDQJb...dJQ4"
}
}
}
When the invocation is received, the service sends an authorization confirmation email to [email protected]
. Alice clicks the link in the email to approve the requested authorization. The service then issues an attestation proving that Alice has authorized requested authorization to did:key:zAli
(an agent DID on new device).
{
"v": "0.9.1",
"iss": "did:web:web3.storage",
"aud": "did:key:zAli",
"att": [
{
"with": "did:web:web3.storage",
"can": "ucan/attest",
"nb": {
"proof": { "/": "bafy...auth" }
}
}
],
"prf": [],
"exp": null,
"s": {
"/": {
"bytes": "7aEDQJb...dJQ4"
}
}
}
In the background, the new device polled access claim and once the request was authorized it received the delegation from an account along with an attestation from the service proving that Alice has authorized it. This allows the new device to access the space.
Alice wants to share access to her space with her friend Bob. She does not know if Bob has ever heard of web3.storage, but she knows his email address [email protected]
allowing her to delegate capabilities:
{
"v": "0.9.1",
"iss": "did:key:zAliceSpace",
"aud": "did:mailto:gmail.com:bob",
"att": [
{
"with": "did:key:zAliceSpace",
"can": "store/list"
}
],
"prf": [],
"exp": 1685602800,
"s": { "/": { "bytes": "7aEDQJb...dJQ4" } }
}
...and the access delegate capability allows her to send the delegation so it can be claimed by Bob.
{
"v": "0.9.1",
"iss": "did:key:zAliceSpace",
"aud": "did:web:web3.storage",
"att": [
{
"with": "did:key:zAliceSpace",
"can": "access/delegate",
"nb": {
// Map of delegation links to be stored for their audiences.
"delegations": { "bafy...prf1": { "/": "bafy...prf1" } }
}
}
],
"prf": [{ "/": "bafy...prf1" }],
"exp": 1685602800,
"s": { "/": { "bytes": "7aEDQJb...dJQ4" } }
}
When Bob runs the w3up
agent the first time and authorizes as [email protected]
, the program invokes the access claim capability and collects all capabilities available to the account, including the one sent by Alice, gaining access to her space.