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.
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:
- Low effort: developers don't need to write and maintain complex access logic
- Familiar: uses very common JSON Web Tokens (JWTs)
- Invisible: users don't need to know that anything special is happening
- Flexible: access can be granted as coarse or granular as the end users wants
- Scalable: no auth server bottleneck / scales infinitely
- Secure: military-grade encryption
- Collaborative: users and services and delegate a subset of their access to others
- 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:
Let's break that down:
Header 📋
This is a standard JWT header, plus a uav
field.
alg
— type of signaturetyp
— state that this is a JWTuav
— "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 validscp
"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:
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 theprf
'saud
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.
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!