Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
An SDK for building apps with decentralized identity and storage
The Webnative SDK empowers developers to build fully distributed web applications without needing a complex back-end. Webnative provides:
User accounts using the browser's Web Crypto API or a blockchain wallet using the Webnative WalletAuth plugin
Authorization using UCAN
Encrypted file storage using the Webnative File System backed by IPLD
Key management using the AWAKE protocol to link devices
Platform APIs for publishing apps from the browser
Webnative applications work offline and store data encrypted for the user by leveraging the power of the web platform.
This guide walks through the fundamental concepts for working with the Webnative. We also publish an API reference at webnative.fission.app and you can view the source code on GitHub.
You can also view the API reference at deno.land and paka.dev.
Have questions? Ask your questions in the Fission Discord server or post them to the Fission Developer forum. We are excited to help you build apps with Webnative and would love to hear from you!
Installing the Webnative SDK
You can add Webnative to a project by loading it from a CDN or installing it with a package manager.
For prototyping or experimentation, you can use the latest version of Webnative.
We recommend linking to a specific version in production to avoid unexpected changes.
Use the build from the global context.
We recommend installing Webnative with a package manager for larger projects that use build tools or bundlers.
Install Webnative with npm
or your preferred package manager.
Import Webnative in your application.
Authentication strategies for registering and linking accounts
An authentication strategy is a set of functions for registering and linking a user's account across devices.
A Webnative program includes an authentication strategy. Webnative provides a WebCrypto authentication strategy by default, but you can configure the strategy using the Components API.
An authentication strategy includes:
accountConsumer. An event emitter that requests device linking from an authed device.
accountProducer. An event emitter that provides device linking to an accountConsumer
.
isUsernameAvailable. Checks if a username is available.
isUsernameValid. Checks if a username is valid.
register. Registers a user with a username and an optional email.
session. Creates a session. Called after registration or device linking.
UCAN. The following sections assume you have a working knowledge of User Controlled Authorization Networks (UCANs). If UCANs are unfamiliar, we strongly recommend you visit ucan.xyz to learn more before continuing.
Webnative registers a user by submitting a username, optional email, and a UCAN authorizing the registration to the Fission server. The UCAN contains a decentralized identifier (DID) which the server registers as the user's DID.
Here is how you register a user:
Our example checks if a username is valid and available, registers a user, and creates a session.
Why is email optional? Most Webnative applications will not need to register user emails. However, we plan to add support for account recovery in a future version of Webnative, and email may become more relevant then.
Each user device has an agent DID associated with the key pair on device. You can query the program to inspect the agent DID:
We call the DID registered with the server the root agent DID. Each time a device makes a request to the Fission server on behalf of a user, it must be the user's initial root device or a device with delegated authority from the root device.
Device linking provides a secure mechanism for linking a user's account across devices or browsers.
Users link devices mainly to access their data on their phone, laptop, tablet, or in another browser. A second benefit is that linked devices are an account backup mechanism. Any linked device can always authorize a new device if a user loses their root device. See the Account Linking section for an example walkthrough of a device linking experience.
Device linking occurs between an account producer (an authed device) and an account consumer (a device that would like to be linked). The devices bootstrap a secure channel to communicate, and the producer issues an account UCAN to the consumer that authorizes it to act on the user's behalf with full capability for 1000 years. So effectively, the newly linked device will have full access to the user's account for as long as it matters.
We want to note that no private keys are ever sent over the wire during device linking. Instead, each device is an agent with its own key pair and associated agent DID. The producer issues an account UCAN that delegates authority to the consumer's agent DID. When the consumer later makes a request to the Fission server, it provides the account UCAN as proof that it can act on the user's behalf.
Distributed applications. We've claimed a few times that Webnative is an SDK for building distributed applications. Device linking uses the AWAKE protocol and is one piece at the heart of what makes Webnative apps distributed. An authed device has all the authority it needs to link new devices. They do not need to ask a server or third party for permission.
When a user links a device, they should open a linking page on the authed device and the device they would like to link. The device to be linked needs to be configured with their username. We recommend the authed device display a QRCode or copy link that encodes the username as a query string param. The user can also manually enter their username on the device to be linked.
During device linking, interfaces must present the user with a PIN challenge. Display the PIN challenge on both devices so the user can confirm the PINs match. The PIN challenge is important for securing the device linking process to prevent person-in-the-middle attacks.
Here is an example of an account producer:
An account producer emits challenge
and link
events. The challenge is a PIN sent by the consumer.
Here is a matching account consumer:
An account consumer also emits challenge
and link
events. The consumer should create a session when the link
event reports approval.
Creating a Webnative program
You can use the Webnative SDK by creating a Webnative program. Here is a minimal example:
A Webnative program is a set of components and utilities for building a distributed web application.
We'll cover each part of a program in upcoming sections. As a quick summary:
You can configure a program with a namespace
parameter and a few optional parameters:
We saw a namespace earlier in our minimal example:
A namespace isolates an application in browser storage. This isolation means you can work on multiple apps on localhost
at the same port and not worry about them interacting or conflicting. Provide each app a namespace, and they will exist independently.
You can also set the namespace as a string:
The debug
flag determines whether Webnative should log detailed debugging information to the console. The default value is false
.
The loadImmediately
flag determines if Webnative should load the filesystem at initialization. The default value is true
.
The version
sets the filesystem version. The version
defaults to the latest stable version of WNFS.
Creating a Webnative session
A session is an authenticated interaction between a user and a Webnative program. Sessions are typically long-lived and are based on a user controlling a key pair.
Webnative authenticates and authorizes a user who controls a key pair through:
WebCrypto. The browser which supports non-exportable private keys.
WalletAuth. A browser extension that supports blockchain wallets through the plugin.
Requesting capabilities. Uses a WebCrypto key pair, but authorization must be requested from another app with equal or greater authority.
A session can be created through an or by . We'll look at each of these approaches in upcoming sections, but for now here is the Session class they create:
A session includes a session type, username, and an optional filesystem.
The session type indicates how the session was created. The possible types include webCrypto
and capabilities
.
WalletAuth uses a blockchain wallet but delegates to a WebCrypto key pair. As a result, the session type when using WalletAuth is webCrypto
.
A username selected by the user at registration. When requesting capabilities, the user will already have a username from the app where they originally registered.
The username is a wallet address when using WalletAuth.
Requesting capabilities from another app
Capabilities is an API that allows apps to request authorization from another app.
Capabilities includes:
collect. Collects UCANs and file system secrets.
request. Requests authorization from another app.
session. Creates a session after authorization has been granted.
We saw earlier in Authentication Strategies how an app can register a user and link their account across devices. Authentication strategies are designed for use within a single web app across multiple devices.
Capabilities are an API for linking between apps. One app requests permission to access some set of resources from a second app with capability equal to or greater than the requested permissions. On user approval, the second app returns capability to the first app in the form of UCANs and file system secrets.
Fission Auth Lobby only. At present, the only app that can provide capabilities to another app is the . We plan to open capabilities provisioning to other apps in a future release.
Here is an example of requesting capabilities from the Fission Auth Lobby:
When Webnative redirects users to the Fission Auth Lobby, they will see a prompt with the permissions that your app has requested. The user can choose to accept or decline the request.
Either way, the Fission Auth Lobby redirects the user to your app. If the user accepted, Webnative will automatically create a session at program initialization.
Become a Webnative jedi 🧘
Check the Webnative version.
Debug logging can be enabled through the configuration object.
When Webnative makes a change to the user's filesystem, it logs:
Next, Webnative links the change to make it available across the web.
When linking completes, the change is published and available to other browsers.
A Webnative program provides shorthand utility functions for easy access:
The shorthands include:
loadFileSystem. Loads the user's file system. Useful when you want to delay loading the filesystem until some time after initialization.
agentDID. Retrieves the agent DID for the current device.
sharingDID. Retrieves the sharing DID for the current device.
WNFS comes with three separate top-level file systems "roots": public, pretty, and private.
Not encrypted. Full metadata support. Starts with /public
.
Not encrypted. No metadata. Represented simply as /p
to be nice and short when creating public URLs like /p/path/to/file.img
. It does not support versioning, use the Public or Private trees for that.
Encrypted. Structured so that file metadata as well as contents are obscured. Starts with /private
.
Since the file system may evolve over time, a "version" is associated with each node in the file system (tracked with semver).
The elements of a distributed app
The Webnative SDK is built on a few foundational blocks, the Webnative File System (), , s, and s. How these pieces are bound to an identity, communicated to other devices and users, encrypted, and transferred, is customizable through components. By default, Webnative uses the Fission infrastructure, but you don't have to.
There are seven components:
Auth. Responsible for , , and .
Capabilities. Determines how are requested and collected.
Crypto. How should private-public key pairs and symmetric keys be stored? How do I sign a message? Which key types should I support for the did:key
method? What are my agent and sharing key pairs?
Depot. Gets IPLD data in and out of your program by creating and referencing CIDs. File system data is brought in and shipped out through this component.
Manners. Various behavioral elements. If the debug
configuration flag is enabled, how does the logging happen, file system hooks, etc.
Reference. Responsible for the sources of truth: the data root (root CID of WNFS), the DID root (your account DID, aka. root agent DID), DNS, and repositories of various items.
Storage. Stores and retrieves ephemeral and session data.
The crypto
component configures Webnative to work with the key pair used by an Ethereum wallet.
The manners
component alters how Webnative transfers the root file system encryption key. Using file system hooks, the key is encrypted and put on the public side of the file system. The wallet can always decrypt this key, enabling file system access no matter what device connects.
In this example, we'll add a file system hook and emojis to the debug messages.
To use a different component than Webnative's defaults, all you have to do is pass them into the program
. Alternatively, if you have a whole composition (see the section below), you can skip a step and directly use the assemble
function. That's what the program
function uses underneath.
A composition is a full set of components.
Migration guides for new versions of the webnative SDK
Some versions of Webnative require apps to migrate their codebase to address breaking changes. Versions with changes are listed below.
This version of Webnative is a major rewrite. Most apps will only need to update their initialization, permission request, and registration code to use the new APIs.
In previous versions, Webnative was initialized using the initialise
or app
functions that returned a state
object:
Initialize a program with permissions
when your app requests capability from the Fission Auth Lobby.
Apps with root authority initialize without permissions
and use authentication strategies to register users and create sessions:
In both cases, the program will have an authenticated session at initialization when a user returns for another visit.
Webnative 0.35 also updates the file system interface so that only Uint8Array
can be written to and read from WNFS. If you are storing strings or other types of data, you will need to convert them Uint8Array
before writes and convert them from Uint8Array
on reads.
For example, writing a string to a file and later reading it looks like this:
The publicReadKey
function in keystore-idb
was renamed topublicExchangeKey
.
Most users will not need to change anything for webnative 0.28.0, unless they are interacting directly with keystore-idb.
The type of App
returned app.index
has changed. Previously, the return type was
In Webnative 0.27.0, the the return type is
The domain for the app is now in the domains
array.
The URL for loading webnative from UNPKG has changed. In Webnative 0.26.0, Webnative was available as index.min.js
.
We found that some projects needed the UMD build and brought it back in Webnative 0.26.1. We have replaced index.min.js
with index.umd.min.js
.
The URL for loading webnative from UNPKG has changed. In previous versions, Webnative was available as index.umd.js
.
In Webnative 0.26, Webnative is available as index.min.js
.
The way paths are used throughout the Webnative and filesystem APIs has changed.
In earlier versions of Webnative, API calls expected UNIX style paths.
Fission Platform APIs
The webnative platform API provides methods to work with Fission apps.
The platform API makes it possible to manage user apps and, combined with the webnative filesystem methods, create and manage apps entirely from the browser.
Yes, you can build an app, that creates apps on behalf of the user! This is a way for developers to make use of the Fission Platform to build their own MicroSaaS business, including custom domains and other features for their users.
The API methods are prefixed with apps
.
apps.index
: A list of all of your apps and their associated domain names
apps.create
: Creates a new app, assigns an initial subdomain, and sets an asset placeholder
apps.publish
: Publish a new app version
apps.deleteByDomain
: Destroy an app identified by domain
Apps that use the platform API must request permission to work with a user's apps. Permissions are requested when a user signs in through the Fission Auth Lobby. See the for more on requesting permissions.
Platform API permissions are requested at permissions.platform.apps
in the .
The value at permissions.platform.apps
can be
"*"
: Grant complete app management access for all of the user's apps
An array of domain names, e.g. [ "an-app.fission.app", "another-app.fission.app" ]
: Grant permission to manage specific apps. Those apps can be published or deleted.
These app function are a bit more low level than your usual Webnative functions. This is because these functions are specifically for the Fission architecture, while the rest of Webnative is not. We'll need two things to get started, the endpoints
and the dependencies
:
apps.index
Lists all user apps and their associated domain names.
Required permissions: { platform: { apps: "*" } }
full app management permissions
Params: No parameters
Returns: App[]
with the domain names, creation and modification times for each app
Example:
apps.create
Creates a new app, assigns an initial subdomain, and publishes a placeholder site.
Required permissions: { platform: { apps: "*" } }
full app management permissions
Params:
subdomain: string | null
optional, set to null
to generate a name
Returns: { domain: string }
the subdomain of the newly created app
Example:
apps.publish
Publishes a new app version by IPFS CID. If the app does not exist yet, create the app first with apps.create
.
Params:
domain: string
required
cid: CID
required
Returns: Nothing returned
Example:
Retrieving the CID depends on where you have staged the app code. One convenient way to do this is to publish the app's HTML, CSS, JavaScript, and assets to a public directory in WNFS and retrieve the CID of that directory.
apps.deleteByDomain
Delete an app by domain.
Params:
url: string
required
Returns: Nothing returned
Example:
How to share private data with the Webnative SDK
You can share a private file or directory with another Fission user:
Private shared data uses public keys, which we call exchange keys, that live in a user's public filesystem under .well-known/exchange/
. Exchange keys are unique per domain because webnative generates a key-pair per domain, so we need to add an exchange key to each domain that will accept shares. We can add an exchange key to a filesystem using:
The auth lobby automatically does this when we create our account, link a device, or authorise an application.
When the receiver of the share has their exchange key(s) set up, we're ready to create a share:
That will do an fs.publish()
and consequently a data root update, so you'll want to wait until those are complete before accepting a share.
The shareDetails
contain a shareId
used to accept or load a share.
By accepting a share, you create a symbolic link (aka. soft link) to the shared data in the /private/Shared with me/SENDER_USERNAME/
directory. In other words, accepting a share is fs.loadShare()
plus copying the soft link from the share:
If you'd like to use the auth lobby to accept, for ease of use like Fission Drive does, then you can create a share link with the share details we got earlier:
When sharing a file or directory with someone, they will receive the updates to those items due to how soft links work. Most filesystem methods work out of the box with soft links, such as get
and read
. But sometimes, you need to resolve them manually, which you can do with fs.resolveSymlink()
. If you want to create a soft link manually, you can do that with:
⚠️ For now soft links are read-only.
Working with the Webnative File System (WNFS)
The Web Native File System (WNFS) is a file system built on top of the InterPlanetary Linked Data model (). Each Webnative user has their own WNFS, and apps store user files and data in it.
Each file system has a public tree and a private tree, much like your macOS, Windows, or Linux desktop file system. The public tree is "live" and publicly accessible on the Internet. The private tree is encrypted so that only the owner can see the contents.
All information (links, data, metadata, etc.) in the private tree is encrypted. Decryption keys are stored so that access to a given directory grants access to all of its subdirectories.
WNFS is structured and functions similarly to a Unix-style file system, with one notable exception: it's a Directed Acyclic Graph (DAG), meaning that a given child can have more than one parent (think symlinks but without the "sym").
A Webnative app assumes full access to the file system unless given a permissions
object in the configuration, then it assumes authorization to parts of the filesystem will be granted when from another app.
WNFS uses directory and file paths built from path segments by path functions.
Path Objects. The path functions create objects like { directory: ["public", "some", "directory"] }
or { file: ["public", "some", "file"] }
. We recommend you use path functions because they validate paths to make sure they are well-formed.
WNFS exposes a POSIX-style interface:
add
: add a file
cat
: retrieve a file
exists
: check if a file or directory exists
ls
: list a directory
mkdir
: create a directory
mv
: move a file or directory
read
: alias for cat
rm
: remove a file or directory
write
: alias for add
The publish
function synchronizes your file system with the Fission API and IPFS. WNFS does not publish changes automatically because it is more practical to batch changes in some cases. For example, a large data set is better published once than over multiple calls to publish
.
Returns: CID
the updated root CID for the file system.
Remember to publish! If you do not call publish
after making changes, user data will not be persisted to WNFS.
Methods for interacting with the filesystem all use absolute paths.
The FileContent
that WNFS can store includes FileContentRaw
, Blob
, string
, number
, and boolean
. FileContentRaw
is Uint8Array
. In addition, the private file system can store Object
s.
add
Adds file content at a given path.
Params:
path: FilePath
required
content: FileContent
required
Returns: CID
the updated root CID for the file system
Example:
cat
Retrieves some file content at a given path.
Params:
path: FilePath
required
Returns: FileContent
Example:
exists
Checks if there is anything located at a given path.
Params:
path: DistinctivePath
required
Returns: boolean
Example:
get
Retrieves the node at the given path, either a File
or Tree
object
Params:
path: DistinctivePath
required
Returns: Tree | File | null
Example:
ls
Returns a list of links at a given directory path
Params:
path: DirectoryPath
required
Returns: { [name: string]: Link }
Object with the file name as the key and its Link
as the value.
Example:
mkdir
Creates a directory at the given path
Params:
path: DirectoryPath
required
Returns: CID
the updated root CID for the file system
Example:
mv
Move a directory or file from one path to another.
Params:
from: DistinctivePath
required
to: DistinctivePath
required
Returns: CID
the updated root CID for the file system
Example:
rm
Removes a file or directory at a given path.
Params:
path: DistinctivePath
required
Returns: CID
the updated root CID for the file system
Example:
write
Alias for add
.
Params:
path: FilePath
required
content: FileContent
required
Returns: CID
the updated root CID for the file system
Example:
Each file and directory has a history
property, which you can use to get an earlier version of that item. We use the delta
variable as the order index, primarily because the timestamps can be slightly out of sequence due to device inconsistencies.
Requesting many versions with file.history.list
can be slow. The acceptable delay will depend on your application.
A session including a username and a filesystem instance. A session will be created from an authentication strategy or requested capabilities depending on which approach your application takes.
A set of functions for registering users and linking devices.
A set of functions for requesting authorization from another app.
The configuration used to instantiate the program.
Component implementations for authentication strategy, capabilities, crypto, storage, depot, manners, and references.
Helper functions provided for easy access.
The filesystem can be loaded after initialization using the program.loadFileSystem
shorthand function. In most cases, you will want to load the filesystem immediately, but deferring can sometimes be useful. For example, you can after initialization.
Permissions are the capabilities to be requested from another application. We'll cover permissions in more detail in the section.
User messages are that report to the user when the current version of Webnative does not support their filesystem. You can update these messages to provide users with your support information.
You can deeply customize a Webnative program by providing component implementations. We'll discuss custom components in the section.
The private crypto
and storage
fields are internal and can be ignored, but they give us an early hint of the Webnative component system described in the section.
A instance. A filesystem is provided by default, but Webnative can be configured without WNFS through the Components API.
More about roots. Learn more about roots in the .
Can I use my file system in a web worker? Yes, but depending on which type of session you want you may need to adjust your setup. To be more specific, when working without you can just create a program in your web worker. But when working with capabilities, it'll depend on how your chosen capabilities flow works. In the case of the current Fission auth lobby, access to the window object is needed when requesting capabilties, so that'll need to happen on the UI/main thread.
Feel free to reach out on our if you need help.
The plugin allows you to use your blockchain wallet to authenticate with webnative. It does not have an auth
component because the identity already exists. But it does have crypto
and manners
components that make the following customizations:
In Webnative 0.35, Webnative is initialized by creating and creating sessions.
See the , , , and sections for more details on the new APIs.
Please review the for an overview of the other changes in this version.
In Webnative 0.24, paths are created by for files and directories.
The docs for the remain available for reference.
Required permissions: Needs either permission for the app domain or full app management permissions. See .
Required permissions: Needs either permission for the app domain or full app management permissions. See .
Here you see a photo being shared from and being accepted by another user in the .
All WNFS operations expect paths created by path functions. See the for more path utility functions.
Paths created by have a FilePath
or DirectoryPath
type. Methods with a DistinctivePath
param accept either a FilePath
or a DirectoryPath
.