A hands-on guide for developers working with zCaps (Authorization Capabilities).
Report issues to this guide's repo: https://github.com/interop-alliance/zcap-developer-guide
- Motivation
- Introduction
- Terminology
- zCap Lifecycle
- Using zCaps with HTTP Requests
- Verifying zCaps on the Resource Server
- Performance Considerations
- Case Studies
- Appendix A: IANA Registration Considerations
- Appendix B: Implementations
- Appendix C: FAQ
- License
Currently, Authorization Capabilities (zCaps for short) are in an awkward but familiar situation where the deployed state of the art is significantly ahead of the specification.
This implementation guide is meant to fill the gap between the spec and its usage in production.
What are Authorization Capabilities? (Aside from a less confusing name for object capabilities.) You can think of them as advanced structured access tokens, with some key features built in, including cryptographic proof of possession, as well as a compact way of chaining proofs together for purposes of delegation.
They are incredibly useful for advanced authorization use cases such as for:
- API microservice ecosystems (REST- or RPC-based) with complex permission models
- Guardrails for Agentic AI
- Decentralized permissioned storage systems such as Encrypted Data Vaults and Wallet Attached Storage
What do we mean by "structured access tokens"? Basically, they're JSON objects (although they can also be serialized to other formats, such as CBOR) with the following properties, which roughly answer the question of "who can perform what actions with a given resource, given these restrictions":
- who - which agent (identified by a cryptographic key or DID) is being given permission
- can - what actions is the agent allowed to perform
- with - what resource are they allowed to perform the actions on
- given - what other restrictions are in place? (these are also known as "caveats" or "attenuations")
Here is a (simplified) example zcap (given in Javascript notation just so we can add comments):
{
// Unique id for the zcap (optional, but often useful)
id: 'urn:uuid:b7576396-c032-46eb-9726-2a628a72828d',
// 'who' - the DID of the agent that is allowed to perform actions
controller: 'did:key:z6MkpmRaHigFewVnmQtLEYS8Zckb4kJNDJCk3bSFeiJNQfZy',
// 'can' - which actions (here, HTTP verbs) they're allowed to perform
allowedAction: ['GET', 'POST'],
// 'with' - what resource can those actions be performed on
invocationTarget: 'https://example.com/api/hello'
}This is a simplified example, in that it's missing things like expiration, or any sense of who granted that permission in the first place, nor does it have any kind of proof chain.
But hopefully you can already get a sense of what this object is for --
you can give it to any app, AI agent, or microservice, that can manage its
own keys (that can prove control over the cryptographic key serialized as
the did:key DID). And now that app can perform
authorized API requests (via http GET and POST actions) to a given API endpoint
(here, https://example.com/api/hello). Specifically, it would include the zcap
in its API requests, either by stuffing it into HTTP headers in case of REST APIs,
or by passing it along as a parameter in case of JSON-RPC or something similar.
And, of course, the requests would need to include a cryptographic proof of control (similar to what DPoP does), so that even if the API requests were intercepted by a third party, the zcaps could not be reused/replayed (as long as the original app did not leak its private keys).
- Currently, a zCap is limited to one invocationTarget.
- Needs to be: multiple invocationTarget-action combinations
- No caveat notation currently used (or rather, implicit attenuation via URL suffixes)
A list of operations that the holder of the zcap is allowed to perform on the target, provided they can provide an invocation signature.
Any entity, usually an app (mobile, desktop or web app), an AI agent, or cloud microservice, capable of generating or storing cryptographic material (at least a public/private keypair) so that it can prove cryptographic control over its identifier.
An HTTP header typically used to carry request authorizations. See Constructing the Authorization Header.
See attenuation.
The DID of the agent authorized to invoke a capability.
A way to cryptographically sign a structured document (like a JSON object), used for chained delegation proofs. See the Verifiable Credential Data Integrity 1.0 specification for more details.
An HTTP header containing a digest hash of the request body. See Constructing the Digest Header.
See Decentralized Identifier 1.1 spec
An optional zcap property with a timestamp determining when a zcap expires.
The timestamp is a string, in XML-Schema dateTimeStamp
format (web developers may be familiar with this format from the Javascript
toISOString()
function).
Refers to RFC9421: HTTP Message Signatures, a specification that details how to sign HTTP requests (headers and body). However, see the Current vs Future Deployments section for more discussion.
The act of invoking a capability at the intended destination (resource server), a combination of presenting the capability, and also proving cryptographic control (usually via a digital signature).
Analogy: a government servant may possess a badge of office in their pocket, but specifically the act of presenting the badge to some other person (and thus proving possession of the badge, even if not cryptographicaly) would be the equivalent of invoking a capability.
Used to sign capability invocations, HTTP headers, and to generate delegation
proofs. For zCaps specifically, this is likely to be an asymmetric key pair,
using an appropriate elliptic curve such as ed25519.
A server hosting a resource that's protected by an authorization capability.
For API use cases, it's the API server itself, for storage use cases, it's
the actual file or database server hosting the individual objects specified in
invocationTarget.
Note: The RS is ultimately responsible for verifying and enforcing zCaps.
A way to revoke (make invalid) a given zcap, after it was issued.
Using zCaps to make authorization-carrying HTTP requests is done in one of two modes: either using a root capability, or using a delegated capability.
Root zcaps are used in cases where full "admin" access is appropriate. All other zcaps are delegated by the agent holding a root zcap. Delegation is expressed in the proof chain section of a zcap.
See Creating a did:key Signer Instance
Short for 'Authorization Capability'. Used generally to refer to a specific capability constructed according to the Authorization Capabilities for Linked Data v0.3 specification. Sometimes used as a general term for a stuctured access token with proof of control and the ability to do delegation chain proofs.
For Object Capability enthusiasts: zCaps are an example of a certificate-based capability, as opposed to platform capabilities such as those used by the OCapN protocol. Don't worry, though, 'certificate-based' just means that it uses a digital signature, you won't actually have to wrangle x509 certificates.
Creating a root zcap is easy:
- Choose a URL for which this zcap is intended. This will determine both
the
invocationTargetand the zcap'sid - Construct the
idlike so:urn:zcap:root:${encodeURIComponent(url)} - Choose the controller DID
- Put it all together using the root zcap template (see below)
Javascript example of how to construct a root zcap, provided you know the URL that it's intended for, and the DID of the controller:
const ROOT_ZCAP_TEMPLATE = {
'@context': [
'https://w3id.org/zcap/v1',
// Assumes you're using an ed25519 based DID; substitute as appropriate
'https://w3id.org/security/suites/ed25519-2020/v1'
],
id: 'urn:zcap:root:...',
controller: 'did:...',
invocationTarget: 'https://example.com/api/endpoint'
};
const url = 'https://example.com/api/endpoint';
const did = 'did:key:z6MknBxrctS4KsfiBsEaXsfnrnfNYTvDjVpLYYUAN6PX2EfG';
const rootCapability = {
...ROOT_ZCAP_TEMPLATE,
id: `urn:zcap:root:${encodeURIComponent(url)}`,
controller: did,
invocationTarget: url
};Example root zcap:
{
"@context": [
"https://w3id.org/zcap/v1", "https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi",
"controller": "did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR",
"invocationTarget": "https://example.com/api"
}import { Ed25519Signature2020 } from '@digitalcredentials/ed25519-signature-2020';
import { ZcapClient } from '@digitalcredentials/ezcap';
const zcapClient = new ZcapClient({
delegationSigner: capabilityDelegationKey.signer(),
SuiteClass: Ed25519Signature2020
});
const allowedActions = ['GET']
// DID identifying the entity to delegate to.
const delegatee = 'did:key:...';
const url = 'https://example.com/api/endpoint';
// Pass in the zcap to delegate - either the original root zcap or its descendant
const capability = rootZcap;
const delegatedCapability = await zcapClient.delegate({
url, capability, targetDelegate: delegatee, allowedActions
});Example delegated zcap:
{
"@context": [
"https://w3id.org/zcap/v1", "https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "urn:zcap:delegated:z9gLKoFmKHwhxCzmo91Ywnh",
"parentCapability": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fdocuments",
"invocationTarget": "https://example.com/documents",
"controller": "did:key:z6MknBxrctS4KsfiBsEaXsfnrnfNYTvDjVpLYYUAN6PX2EfG",
"expires": "2022-11-28T20:53:06Z",
"allowedAction": ["read"],
"proof": {
"type": "Ed25519Signature2020",
"created": "2021-11-28T20:53:06Z",
"verificationMethod": "did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR#z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR",
"proofPurpose": "capabilityDelegation",
"capabilityChain": [
"urn:zcap:root:https%3A%2F%2Fexample.com%2Fdocuments"
],
"proofValue": "z244yxzRuFMyGfK85QcE6UewEZ3JpGDDTCvBKuxNiwdnxF3AmsSAoVYTBPLvFpYV7SeeWB4tUBGMGTF7pka6xR3av"
}
}How does an agent request a zCap? (From the resource server's controller or similar appropriate entity.)
- For many use cases, zCaps do not need to be dynamically requested. Instead, they are created/delegated at service provisioning time, and the resulting zCap in a config file (environment variable or your secrets management infrastructure)
- For user-facing workflows, consider using VC-API in combination with the Authorization Capability Request query from the VPR spec.
- Other workflows and protocols, such as the Grant Negotiation and Authorization Protocol (GNAP) can also be used to request zCaps.
Example zCap request:
{
"verifiablePresentationRequest": {
"interact": {
"type": "UnmediatedHttpPresentationService2021",
"serviceEndpoint": "https://example.com/exchanges/tx/12345"
},
"query": [
{
"type": "ZcapQuery",
"capabilityQuery": {
"reason":
"Example App is requesting the permission to read and write to the Verifiable Credentials and VC Evidence collections.",
"allowedAction": ["GET", "PUT", "POST"],
"controller": "did:example:12345",
"invocationTarget":
{ "type": "urn:was:collection", "contentType": "application/vc", "name": "VerifiableCredential collection"}
}
}
]
}
}- Out of band
- Resource Server is responsible for handling its own revocation API / logic
Although this guide goes into the details (below) of how to construct an HTTP request using zCaps for authorization, developers are likely to interact with zCaps using some sort of REST client with a wrapper that constructs the necessary headers.
Javascript example, using a root capability to make requests. Note that to use a root zcap, the client needs to know just two things:
- Which cryptographic key type to use (that's the
Ed25519Signature2020suite) - A signer, which is an abstract handle to a signature function. Signers are either provisioned at config time (with a private key loaded from an environment secret), or, preferably, used via an HSM (Hardware Security Module).
import { Ed25519Signature2020 } from '@digitalcredentials/ed25519-signature-2020'
import { ZcapClient } from '@digitalcredentials/ezcap'
const rootSigner = await loadOrConstructRootSigner()
// Construct a client, pass it the ability to sign requests (via invocationSigner)
const zcapClient = new ZcapClient({
SuiteClass: Ed25519Signature2020, invocationSigner: rootSigner
})
// You can now perform authorization-carrying requests
const url = 'https://example.com/api/protected-endpoint'
const response = await zcapClient.request({
url, method: 'GET', action: 'GET'
})
console.log(response)When using a delegated zcap, you will need to also include it in each request. Example:
const capability = await loadDelegatedCapabilityFromConfig()
const invocationSigner = await loadSignerFromConfig()
const zcapClient = new ZcapClient({
SuiteClass: Ed25519Signature2020, invocationSigner
})
// You can also include additional custom headers
const response = await zcapClient.request({
url, capability, headers,
method: 'POST', action: 'POST', json: { hello: "world" }
})
console.log(response)Note: This section is mostly for the benefit of zCap library implementers.
Developers wishing to use zCaps to make requests are encouraged to use existing
libraries whenever possible, such as the @digitalcredentials/ezcap Javascript
library.
To create an authorized HTTP request by invoking a given zcap, follow this general algorithm:
- Construct the
Capability-Invocationheader - Construct the
Digestheader if applicable (only if your request has a body/payload -- applicable for PUT/POST but not for GET) - Assemble the pseudo-headers and headers to sign:
['(key-id)', '(created)', '(expires)', '(request-target)','host', 'capability-invocation']- If request has a body, add
'content-type', 'digest'headers to the above list
- If request has a body, add
- Create the signature string (see below for details)
- Construct the
Authorizationheader, add the signature and other relevant parameters - Perform the HTTP request, include the
Capability-Invocation,Authorization, and (optionally, if request has a body) theDigestheaders
- Current zCap deployments and implementation libraries use the
Digestheader from thedraft-ietf-httpbis-digest-headers-05draft spec, and use theAuthorizationheader from the Cavage HTTP Signatures Draft 12 spec - Future iterations of the zCap spec and implementations are expected to migrate
to use RFC 9421: HTTP Message Signatures
and its corresponding
Signature-Input,Signature, andContent-Digestheaders instead.
For root zcap invocations, use the zcap id by itself:
Capability-Invocation: zcap id="urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi"
For delegated (non-root) zcaps, include the full encoded gzip'd capability, as well as the action being invoked.
Example using JS string templates to construct the header for performing a GET
action:
const encodedCapability = base64UrlEncode(gzip(JSON.stringify(capability)))
headers['capability-invocation'] = `zcap capability="${encodedCapability}",action="GET"`- Only relevant/recommended if your request has a payload or request body (that is, if it's a PUT or POST request, etc)
- See the
draft-ietf-httpbis-digest-headers-05draft spec for details - Current deployments use either:
- the
SHA-256hash method with base64url encoding, or - Multihash encoding (also using
sha256)
- the
Example base64url encoded Digest header:
Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
Example Multihash encoded Digest header:
Digest: mh=uEiBfjwT2o6iSqqu922zyc4lEk3c5YNSjJbEF_uRu70ME8Q
- Current deployments still use the Cavage HTTP Signatures Draft 12 spec
Example Authorization header:
Authorization: Signature keyId="...",algorithm="rsa-sha256",headers="...",created="...",expires="...",signature="..."
Developers familiar with common API authorization schemes might well ask, "how is this better than OAuth 2?".
...
- The current spec version, Authorization Capabilities for Linked Data v0.3, uses JSON-LD serializations for zCaps, primarily for the convenience of using various Data Integrity cryptosuites for proof chains.
- However, now that the W3C Verifiable Credentials Working Group has released
specs that don't require linked data canonicalization (such as the
eddsa-jcs-2022suite), proof chains can be done without the use of@contextor JSON-LD. - (From conversations with the zCap spec editors) The next version of the zCap
spec is going to drop the
@contextrequirement, and use either JCS-based signature methods, or perhaps specify a default context.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Examples and code snippets are licensed under the MIT license.
