NOTE: this article does not described the current specification of UCAN, which can be found at https://github.com/ucan-wg/spec/. The overall concepts are the same, but many of the nuts-and-bolts details have changed.

Fission is building a system which "makes the right thing the easy thing." It lets you write apps for the browser without having to write or deploy a back end. We're making use of fairly recent browser features and W3C standards to make this all possible. Read on for a technical summary, or join us in the developer forum to get into more detail.

One of the most common tasks for apps is authorizing users to perform some action, like storing new data to storage, updating records, or fetching a file.

Traditional app architecture has many users share one database ("multi-tenant"), with all user data fully interleaved with each other. Authorization here is primarily focused on keeping users from editing each other's records on this shared infrastructure. The server's rules give fairly coarse-grained control. Due to the inevitable exceptions to these rules, the logic becomes increasingly complex over time.

Even in a microservice architecture, typically all requests are funneled through a central authorization service. Over time this causes several challenges, including complex logic, cost of maintenance, tricky edge cases, and difficulty managing traffic spikes. In short: it doesn't scale well.

Even incumbents like Google are moving away from the traditional auth server model to overcome the above challenges. Fission has different constraints from Google and Amazon, but can adapt a lot of these ideas for our purposes. Essentially they're moving from a central auth server setup to a distributed model where more power is delegated to services.

What if we learn from Google's approach (plus older approaches like SDSI/SPKI) but took it to its logic conclusion?

Introducing UCANs

User Controlled Authorization Networks (UCANs) are a way of doing authorization where users are fully in control. OAuth is designed for a centralized world, UCAN is the distributed user controlled version.
UCAN Sam

At a high level, User Controlled Authorization Networks (UCANs) are a way of doing authorization ("what you can do") where users are fully in control. There's no all-powerful authorization server, or server of any kind required. Everything that a users is allowed to do is captured directly in a key or token, and can be sent to anyone that knows how to interpret this format.

Since all Fission accounts are equipped with a global ID and cryptographic keys, we were able to design a system that has very few assumptions and thus works in a huge number of situations.

This setup has several advantages:

  1. Low effort: developers don't need to write and maintain complex access logic
  2. Familiar: uses very common JSON Web Tokens (JWTs)
  3. Invisible: users don't need to know that anything special is happening
  4. Flexible: access can be granted as coarse or granular as the end users wants
  5. Scalable: no auth server bottleneck / scales infinitely
  6. Secure: military-grade encryption
  7. Collaborative: users and services and delegate a subset of their access to others
  8. Self-contained: the token contains all the information needed to verify it

UCANs are all that we need to sign into multiple machines, delegate access for service providers to do things while we're offline, securely collaborate on documents with a team, and more. We get the flexibility of fine- or coarse-grained control, all controlled by the one who cares about the data the most: the user.

We've implemented this as the authorization system for Fission, and are also making this available as a building block for developers to solve user authorization and delegation within their own applications.


This system of authorization is broken into two halves: read and write (or "command and query", depending on your background). Without getting too in the weeds, here's a high level description of how this all works:

Read (Query) Access

Read access comes in three flavours: public, private, and unlisted. Access follows capability-based security, where anyone with the reference (URL or CID) and cryptographic key can read the data by virtue of having access to these.

Public 👀

Public files are just that: files that can be discovered or accessed by anyone at an easy-to read path.

Here's a live example:

https://boris.files.fission.name/p/ReactionGIFs/itcrowd-turn-it-off.gif

Private 🔐

The user encrypts the data, and shares the key with those that should have access. The contents of a private directory is only readable with a key, but once you have access to that directory, all of the data in that directory (including subdirectories and metadata) is accessible.

The end experience matches the behaviour in other online consumer file storage solutions like Dropbox and Google Drive. A major difference with Fission is that the end user is given complete control over who has access, and access does not depend on Fission's servers being accessible (i.e. you're offline or Fission disappears).

Unlisted 🗺️

Sort of a mashup of public and private files! This is useful when you want to (e.g.) embed an image in an email without distributing keys, but also don't want it easily discoverable.

The unlisted files themselves are left unencrypted, but finding them is practically infeasible without the private index. Think of this index as a secret treasure map for the web — the map itself private (only a select few have the map), but anyone with it can find the data at the marked locations.

Under the hood, the Fission SDK creates a JSON file that lists all of the locations. That JSON file is then encrypted and the key passed to whoever should have access., or the links can be shared directly.

Here's a simple example of what this looks like:

// The "treasure map"
{
  "QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ": {
      "cat.jpg": "Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u"
  }
}
# URL
https://ipfs.runfission.com/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg

Write (Command) Access 🖊️

There are some actions that a user needs the help of another user or service to perform. For example: sending an email, or updating DNS.

In a traditional OAuth based system, the "account" lives entirely on the server, and the user is granted access with a token. In Fission's design, the account is a key pair, and a UCAN is equivalent to an OAuth token. OAuth is designed for a centralized client/server world. UCANs are the distributed user controlled equivalent.

UCANs are simply JWTs that contain special keys. Much of this will look familiar if you've done web auth in the past decade or so. Here's an example:

{
  "alg": "Ed25519",
  "typ": "JWT"
  "uav": "0.1.0"
}
{
  "aud": "did:key:zStEZpzSMtTt9k2vszgvCwF4fLQQSyA15W5AQ4z3AR6Bx4eFJ5crJFbuGxKmbma4",
  "iss": "did:key:z5C4fuP2DDJChhMBCwAkpYUMuJZdNWWH5NeYjUyY8btYfzDh3aHwT5picHr9Ttjq",
  "nbf": 1588713622,
  "exp": 1589000000,
  "scp": "/"
  "ptc": "APPEND",
  "prf": null,
}
Example UCAN JSON Web Token
Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjAuMS4wIn0.eyJhdWQiOiJkaW
Q6a2V5OnpTdEVacHpTTXRUdDlrMnZzemd2Q3dGNGZMUVFTeUExNVc1QVE0ejNBUjZCeDRl
Rko1Y3JKRmJ1R3hLbWJtYTQiLCJpc3MiOiJkaWQ6a2V5Ono1QzRmdVAyRERKQ2hoTUJDd0
FrcFlVTXVKWmROV1dINU5lWWpVeVk4YnRZZnpEaDNhSHdUNXBpY0hyOVR0anEiLCJuYmYi
OjE1ODg3MTM2MjIsImV4cCI6MTU4OTAwMDAwMCwic2NwIjoiLyIsInB0YyI6IkFQUEVORC
IsInByZiI6bnVsbH0.Ay8C5ajYWHxtD8y0msla5IJ8VFffTHgVq448Hlr818JtNaTUzNIw
FiuutEMECGTy69hV9Xu9bxGxTe0TpC7AzV34p0wSFax075mC3w9JYB8yqck_MEBg_dZ1xl
JCfDve60AHseKPtbr2emp6hZVfTpQGZzusstimAxyYPrQUWv9wqTFmin0Ls-loAWamleUZ
oE1Tarlp_0h9SeV614RfRTC0e3x_VP9Ra_84JhJHZ7kiLf44TnyPl_9AbzuMdDwCvu-zX
jd_jMlDyYcuwamJ15XqrgykLOm0WTREgr_sNLVciXBXd6EQ-Zh2L7hd38noJm1P_MIr9_
EDRWAhoRLXPQ
The same, as a bearer token (for an HTTP Authorization header)

Let's break that down:

Header 📋

This is a standard JWT header, plus a uav field.

  • alg — type of signature
  • typ — state that this is a JWT
  • uav — "UCAN version" (so we can track the format of when it was issued)

Body 💪

  • aud "Audience" — the ID of who it's intended for (the "to" field)
  • iss "Issuer" — ID of who sent it (the "from" field)
  • nbf "Not Before" — Unix timestamp of when it becomes valid (typically when it was created, but not always)
  • exp "Expiry" — Unix timestamp of when it stops being valid
  • scp "Scope" — The scope of things it's able to change (e.g. a file system path)
  • ptc "Potency" — what rights comes with the token (in this case it's append only)
  • prf "Proof" — an optional nested token with equal or greater privileges

These are then all signed with the user's private key. This key must match the public key in the iss field (user IDs are public keys), directly authenticating the token. As the token is a complete description of access, this token is self-validating with no need to look at other data or services.

Delegation 🤝

What if you want to grant another user or service the ability to perform some action on your behalf? As long as they have a valid UCAN, they can wrap it in another with equal or lesser rights and include the original in the prf field.

Since every UCAN layer is self-signed, we can trace back to the root (no prf field), and know who the delegate is acting as. This chain of tokens is itself is the proof that you're perform some action.

For example, here's a chain:

"prf":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.
       eyJhdWQiOiJkaWQ6a2V5OnpTdEVacHpTTXRUdDlrMnZz
       emd2Q3dGNGZMUVFTeUExNVc1QVE0ejNBUjZCeDRlRko1
    Y3JKRmJ1R3hLbWJtYTQiLCJleHAiOjE1ODgyNjU0MjAs
     ImlzcyI6ImRpZDprZXk6ejFMQm53RWt0d1lMcnBQaHV3
     Rm93ZFZ3QUZYNXpwUm85cnJWendpUlJCQmlhQm9DUjdo
     TnFnc3RXN1ZNM1Q5YXVOYnFUbVFXNHZkSGI2MVJoVE1W
 Z0NwMUJUeHVhS1UzYW5Xb0VSRlhwdVp2ZkUzOWc4dTdI
      UzlCZUQxUUpOMWZYNlM4dnZza2FQaHhGa3dMdEdyNFpm
     ZmtVRTU3V1pwTldNNlU2QnFka3RaeG1LenhDODV6TjRG
    QzlXczVMSHVHZnhhQ3VCTGlVZkE3cUVZVlN6MVF1MXJa
    RHBENk55ZlVOckhKMUVyWmR1SnVuOTc2bmJGSHJtRG5V
    NDdSY1NNRkVTYk5LRGkxNDY3dFJmdWJzTXJEemViZENG
    S1EybTFBWXlzdG8yaTZXbWNudWNqdDN0bndUcWU3Qm84
    TDFnOGg4VUdBOTQ2REYzV2VHYlBVR3F6bnNVZExxNlhM
    Q21KekprSm1yTnllWmtzd2R5UFgyVnU2SjlFNEoxMlNi
     M2g5ZHM3YXRCeWFtZnRpdEVac2Y2aFBKa0xVWEdUaFlw
     Q25tUkFBclJSZlBZMkg2Y0tEYzdBY25GUHlOSEdrYWI1
    WkZvNHF2Z0JaeXRiSzFLNW9EM0hmUTZFMnliTGh5QzJi
    OGk1d282REx0bTl1Zml4U0pOTlRIN1Vpa2s4OENtZXJ0
    S1I3czEyQ0sxV0xFTTNadTVZQlpOcGhuamo3Y3A4UVRv
     ZEFlaFJQVjlORzFDTEVBTUpWTjc5RHZZZTZTZmlhZkpv
     YmN2ZkQ4bnBmUzZqY2VqY3lvdVFiRXBLREc3UUFuS1M0
    OFA0QXZnQnFEdmZOVWU1NGpNa2s2cjZDb1g0TGNZR0h1
    a1pERW5lYTlrd2tFb1hrVVlTNGoxQWZiS2g0NEZ6U3VY
    YlFxWm5qalZwVGh4Q05tbU5uMUU0cUhtc0ZrdkdvRjNG
     TjU1Q1Brb0dmREN2eVFKZ3Ftc0ZtcGVUSlN5OXd6djRN
    dmJxcHVBVHhyN2V5eHNHZUNXUWtjRHd1YjMyaW5HcFIz
     cmVUZnpSSkVDQ0ZaYXJuWGRjQzVQaWRha2IxV3U4TCIs
     Im5iZiI6MCwicHRjIjoiQVBQRU5EIiwicHJmIjpudWxs
     LCJzY3AiOiIvIn0.leyE9w2TF28espPq6mOWziQuJny2
     GHH_wajV6S9q4gF9SLP-i9JaX_XbkHlE1GhpQ36gSs6F
     v4_AXSuJzDkUhnAA-oPsI5bSHl28XbobzqdmXtQ2liK-
     Gum7kUtF1CPXlIamV0NIUlCKLlaUgFod5ZQvvA19kMHU
     ugDGm8O3G98TSm3qLlG-eoFNVXr0NSpvLeui3kQbdBsP
     GMykaTsUn1fNLI3oKkK6JvUIq4po6gIidTdOJDlS7y_W
     4bdMXUQcTprtpd2QmTqwTzws9tu4GBdx7q1vz35LiG39
     ohhRs2NKB4rxbZK2O9kX1G2xLMSETE_YT9GR04XWMnFo
     eIodsg"
Nested proof

You'll notice that the nested proof is encoded as a bearer token. This is because it needs to include its signature to prove that it's valid, and a JWT signature is on the content encoded this way.

This token is thus valid as long as:

  • All token signatures are correct
  • The time range, potency, and scope of prf are greater-or-equal to the enclosing token
  • The outer token's iss field matches the prf's aud field (chain "to" and "from" correctly)
  • The timestamps are valid at the present time

Hashing ️ 🏎️

These chains can get large, so you can optionally hash the outermost one before sending to a server. This acts as a "content address", meaning that if the service hasn't seen it before, it can separately request that token, but if it already has it in cache and doesn't need to get it over the network. Since hashes are much smaller than their content, this can save a lot of bandwidth on repeated requests.

"prf":"QmU5WJTTp9vtMN1PBJpTV9xWXbTFBcWx3qjPGuXJXtujyd"
Same as the example above, but with the proof compressed to a content address

Conclusion

UCANs are a straightforward way of doing authorization that leverage the public key infrastructure already baked into Fission. This is essentially authorization-at-the-edge with familiar JWTs. Since the token is self-contained, it's infinitely scalable. It's also very flexible: the user can grant root access to everything, or grant a tab write access to a single object for one minute.

This article covers everything that you need to use a UCAN. For those interested in the space at a deeper level, there's a lot more background and technical design thinking that we didn't cover here. Keep an eye on our developer forum for an upcoming article detailing the deeper internals!