ts-ucan
UCANs are JWTs that contain special keys.
At a high level, UCANs (“User Controlled Authorization Network”) are an authorization scheme ("what you can do") where users are fully in control. UCANs use DIDs ("Decentralized Identifiers") to identify users and services ("who you are").
No all-powerful authorization server or server of any kind is required for UCANs. Instead, everything a user can do is captured directly in a key or token, which can be sent to anyone who knows how to interpret the UCAN format. Because UCANs are self-contained, they are easy to consume permissionlessly, and they work well offline and in distributed systems.
UCANs work
- Server → Server
- Client → Server
- Peer-to-peer
OAuth is designed for a centralized world, UCAN is the distributed user-controlled version.
Read more in the whitepaper: https://whitepaper.fission.codes/access-control/ucan
Structure
alg
, Algorithm, the type of signature.
typ
, Type, the type of this data structure, JWT.
uav
, UCAN version.
Payload
att
, Attenuation, a list of resources and capabilities that the ucan grants.
aud
, Audience, the DID of who it's intended for.
exp
, Expiry, unix timestamp of when the jwt is no longer valid.
fct
, Facts, an array of extra facts or information to attach to the jwt.
iss
, Issuer, the DID of who sent this.
nbf
, Not Before, unix timestamp of when the jwt becomes valid.
prf
, Proof, an optional nested token with equal or greater privileges.
Signature
A signature (using alg
) of the base64 encoded header and payload concatenated together and delimited by .
Build
ucan.build
can be used to help in formatting and signing a UCAN. It takes the following parameters:
type BuildParams = {
issuer: Keypair
audience: string
capabilities?: Array<Capability>
lifetimeInSeconds?: number
expiration?: number
notBefore?: number
facts?: Array<Fact>
proofs?: Array<string>
addNonce?: boolean
}
Capabilities
capabilities
is an array of resource pointers and abilities:
{
with: { scheme: "mailto", hierPart: "boris@fission.codes" },
can: { namespace: "msg", segments: [ "SEND" ] }
}
Installation
NPM:
npm install --save ucans
yarn:
yarn add ucans
Example
import * as ucans from "ucans"
const keypair = await ucans.EdKeypair.create()
const ucan = await ucans.build({
audience: "did:key:zabcde...",
issuer: keypair,
capabilities: [
{
with: { scheme: "wnfs", hierPart: "//boris.fission.name/public/photos/" },
can: { namespace: "wnfs", segments: [ "OVERWRITE" ] }
},
{
with: { scheme: "wnfs", hierPart: "//boris.fission.name/private/6m-mLXYuXi5m6vxgRTfJ7k_xzbmpk7LeD3qYt0TM1M0" },
can: { namespace: "wnfs", segments: [ "APPEND" ] }
},
{
with: { scheme: "mailto", hierPart: "boris@fission.codes" },
can: { namespace: "msg", segments: [ "SEND" ] }
}
]
})
const token = ucans.encode(ucan)
const payload = await ucans.buildPayload(...)
const ucan = await ucans.sign(payload, keyType, signingFn)
Verifying UCAN Invocations
Using a UCAN to authorize an action is called "invocation".
To verify invocations, you need to use the verify
function.
import * as ucans from "ucans"
const serviceDID = "did:key:zabcde..."
const ucan = ucans.build({ ... })
const encoded = ucans.encode(ucan)
const result = await ucans.verify(encoded, {
audience: serviceDID,
isRevoked: async ucan => false
requiredCapabilities: [
{
capability: {
with: { scheme: "mailto", hierPart: "boris@fission.codes" },
can: { namespace: "msg", segments: [ "SEND" ] }
},
rootIssuer: borisDID,
}
],
)
if (result.ok) {
} else {
}
Delegation Semantics
UCAN capabilities can have arbitrary semantics for delegation.
These semantics can be configured via a record of two functions:
canDelegateResource(parent: ResourcePointer, child: ResourcePointer): boolean
andcanDelegateAbility(parent: Ability, child: Ability): boolean
.
Which specify exactly which delegations are valid.
(This doesn't support rights amplification yet, where multiple capabilities
in combination may result in a delegation being possible. Please talk to us
with your use-case and ideas for how a good API for that may work.)
import * as ucans from "ucans"
const PATH_SEMANTICS = {
canDelegateResource: (parentRes, childRes) => {
if (parentRes.with.scheme !== "path" || childRes.with.scheme !== "path") {
return ucans.equalCanDelegate.canDelegateResource(parentRes, childRes)
}
if (parentRes.hierPart === ucans.capability.superUser.SUPERUSER) {
return true
}
if (`${childRes.hierPart}/`.startsWith(`${parentRes.hierPart}/`)) {
return true
}
return false
},
canDelegateAbility: equalCanDelegate.canDelegateAbility
}
Sponsors that contribute developer time or resources to this implementation of UCANs:
UCAN Toucan