Skip to content

UCAN Token Architecture

Sthalam uses UCAN (User Controlled Authorization Networks) tokens for capability-based access control. Unlike traditional permission systems that require centralized servers to check access rights, UCAN tokens are self-contained authorization certificates that encode all permission logic directly in the token structure.

The key innovation in Sthalam is using UCAN token verbosity to enable intelligent, decentralized sync decision-making. By parsing capability strings in tokens, viewers automatically know:

  • What to sync: Extract resource/folder IDs from capabilities
  • What to merge: Write permissions enable bidirectional synchronization
  • What to ignore: Read-only permissions indicate one-way data flow
  • Document-specific behavior: Different permissions for content vs. comments vs. submissions

This eliminates the need for a centralized permission server—the tokens themselves contain all the logic needed for correct sync behavior.

3-document architecture diagram showing resource creation with 3 documents, permission model, UCAN token structure, and sync behavior by document

Every Sthalam resource (website, post, newsletter) uses three separate Yjs documents with distinct permission models:

Purpose: Website structure, blog posts, newsletter content

Viewer Permission: crud/read (read-only)

UCAN Capability:

sthalam:resource:abc123:blocksuite_doc - crud/read

Sync Behavior (Local-First + State Vector Sync):

  • First time: Full resource sent to viewer
  • Viewer renders: Always from local blocksuite_doc first (instant load)
  • Background sync: Viewer sends state vector to node
  • Publisher edits → Writes to local first, syncs via state vector exchange
  • State vector exchange: Viewer sends state vector, node responds with missing CRDT diffs
  • Apply updates: Viewer applies CRDT diffs to local copy
  • One-way state vector flow: Publisher writes local & syncs via state vectors, viewer receives diffs via state vector exchange

Use Case: Main website content that viewers read but cannot modify

2. thread_comments_doc (Collaborative Comments)

Section titled “2. thread_comments_doc (Collaborative Comments)”

Purpose: Real-time comment threads and discussions between publisher and multiple viewers. A single document can have multiple independent threads (e.g., separate discussions for different sections)

Viewer Permission: crud/write (bidirectional)

UCAN Capability:

sthalam:resource:abc123:thread_comments_doc - crud/write

Sync Behavior (Local-First + State Vector Multi-Thread):

  • First time: Full resource sent
  • Render: Always from local thread_comments_doc first (instant load)
  • Write to local: Comments written to LOCAL Yjs doc first (instant UI update)
  • State vector exchange: All participants send state vectors, receive only missing CRDT diffs
  • Bidirectional: Both viewer and node exchange state vectors and respond with diffs
  • Any participant writes → Writes to local with thread_id, syncs via state vector
  • Apply updates: CRDT diffs applied to local copy
  • Client-side filtering → Comments filtered by thread_id for display
  • All can read and write to any thread
  • Multi-participant state vector flow: All write local, exchange state vectors, receive only missing diffs
  • Viewers see each other’s comments: Multiple viewers collaborate across all threads via state vector sync
  • Never full doc: Only state vectors and CRDT diffs exchanged

Use Case: Collaborative discussions where publisher and multiple viewers contribute across multiple independent threads, with viewers able to discuss with each other in organized topic-specific conversations. All sync happens via efficient state vector-based CRDT protocol - comments written to local first for instant UI, then synced in background.

Purpose: Form responses and feedback collection. A single document can have multiple forms (e.g., contact form, feedback form, survey) differentiated by form_id, with field metadata for parsing

Viewer Permission: crud/append (append-only)

UCAN Capability:

sthalam:resource:abc123:form_submissions_doc - crud/append

Sync Behavior (Local-First + Incremental Multi-Form):

  • First time: Full resource sent to publisher (viewers don’t receive this doc)
  • Publisher renders: Always from local form_submissions_doc first (instant load)
  • Viewer fills form → Pushes incremental CRDT update with form_id and field metadata (append operation)
  • Node stores incremental diff
  • Publisher background poll → Pulls only incremental diffs, not full doc
  • Apply updates: Publisher applies CRDT diffs to local copy
  • Filter by form_id: Display submissions grouped by form
  • Parse with metadata: Use field parse_as for structured display
  • No updates back to viewer (append-only, viewer never pulls)
  • One-way incremental push and pull: Viewer pushes diffs to node, publisher polls and pulls diffs, no return sync

Use Case: Collecting feedback across multiple forms without exposing other viewers’ submissions. All forms stored in single doc, organized by form_id with field metadata for parsing.

When publishers generate connection strings, they create folder tokens:

{
"alg": "EdDSA",
"typ": "JWT",
"aud": "*",
"cap": {
"sthalam:folder:folder_uuid": {
"view/public": [{}]
}
},
"exp": 1759045632,
"iss": "publisher_did",
"ucv": "0.10.0-canary"
}

Key Properties:

  • Wildcard Audience: * allows anyone with the token
  • Public View: view/public capability for folder access
  • 30-Day Expiry: Reasonable validity period for sharing
  • Folder Scope: Grants access to all resources in folder

When nodes prepare resources for viewers, they generate resource-specific tokens:

{
"alg": "EdDSA",
"typ": "JWT",
"aud": "viewer_did",
"cap": {
"sthalam:resource:abc123:blocksuite_doc": {
"crud/read": [{}]
},
"sthalam:resource:abc123:thread_comments_doc": {
"crud/write": [{}]
},
"sthalam:resource:abc123:form_submissions_doc": {
"crud/append": [{}]
}
},
"exp": 2702046588,
"iss": "publisher_did",
"prf": ["folder_token_cid"],
"ucv": "0.10.0-canary"
}

Key Properties:

  • Specific Audience: Targeted to viewer’s DID
  • Multiple Capabilities: One capability per document type
  • Granular Permissions: Different access levels per document
  • Proof Chain: Links back to folder token for validation
  • Long Lifetime: 30-year validity for persistent access

The verbosity of capability strings enables automatic sync behavior:

From capability strings like:

sthalam:resource:abc123:blocksuite_doc
sthalam:resource:abc123:thread_comments_doc
sthalam:resource:abc123:form_submissions_doc

The system extracts:

  • Domain: sthalam
  • Resource Type: resource
  • Resource ID: abc123
  • Document Type: blocksuite_doc, thread_comments_doc, form_submissions_doc

Code Reference (crypto_utils/src/ucan_utils.rs:456-499):

pub fn extract_resource_id_from_ucan(ucan: &Ucan) -> Result<String, UcanError> {
for capability in ucan.capabilities().iter() {
let cap_resource = capability.resource;
if cap_resource.contains(":resource:") {
let parts: Vec<&str> = cap_resource.split(':').collect();
// Supports:
// - "domain:resource:resource_id" (3 parts)
// - "domain:resource:resource_id:doc_type" (4 parts)
if parts.len() >= 3 && parts[1] == "resource" {
let resource_id = parts[2];
return Ok(resource_id.to_string());
}
}
}
Err(UcanError::CapabilityNotFound)
}

By checking the ability field for each document:

crud/read:

  • Viewer renders from local first (instant)
  • Viewer sends state vector to node in background
  • Node responds with missing CRDT diffs
  • Viewer does not write modifications
  • Unidirectional state vector sync: Publisher writes & syncs via state vectors, viewer receives diffs

crud/write:

  • All participants write to local first (instant UI update)
  • All participants exchange state vectors with node
  • Node responds with only missing CRDT diffs
  • All participants send their own CRDT diffs node doesn’t have
  • Bidirectional state vector sync: All write local, exchange state vectors, receive only missing diffs
  • Never full doc: Only state vectors and diffs transferred

crud/append:

  • Viewer pushes incremental append operations to node
  • Viewer does not sync this doc (append-only, no reads)
  • Publisher syncs via state vectors to receive submissions
  • Unidirectional: Viewer pushes diffs, publisher receives via state vector sync

Every sync message includes the resource UCAN for validation:

Node Validates Incoming Updates (network/src/p2p/website_handler.rs:866-963):

async fn process_viewer_comments_update(
&self,
resource_id: String,
resource_ucan: String,
sync_data: String,
) -> P2PResult<()> {
// 1. Validate resource UCAN structure
let ucan = crypto_utils::ucan_utils::validate_structure(&resource_ucan).await?;
// 2. Extract resource_id and verify match
let ucan_resource_id = crypto_utils::ucan_utils::extract_resource_id_from_ucan(&ucan)?;
if ucan_resource_id != resource_id {
return Err("Resource ID mismatch");
}
// 3. Check capability (implicit: must have crud/write or crud/append)
// 4. Apply updates only if UCAN is valid
services::apply_updates(&resource_id, &sync_data, &local_user.id, repo_ctx, &crypto_utils).await?;
}

Viewer Validates Received Updates: Similar validation on viewer side ensures updates come from authorized publisher.

Traditional ACL Systems:

  • Require centralized permission server
  • Must query server for every operation
  • Single point of failure
  • Network latency for permission checks

UCAN-Based System:

  • Self-contained authorization
  • No server queries needed
  • Offline verification possible
  • Cryptographic proof of authority

Resource tokens chain back to folder tokens, which chain to publisher’s root:

Publisher Root Authority
↓ (issues)
Folder Token (view/public for folder_uuid)
↓ (delegates)
Resource Token (crud/read + crud/write + crud/append for resource_uuid)
↓ (presented by)
Viewer

Each link in the chain is cryptographically signed, creating an audit trail back to the root authority.

No centralized permission server required—viewers autonomously determine correct sync behavior by parsing their UCAN tokens.

Explicit: Each document type has explicit capability string

  • Easy to audit and understand
  • Clear permission boundaries
  • No ambiguity in what’s allowed

Implicit (traditional): Permissions inferred from context

  • Harder to debug permission issues
  • Potential for unintended access
  • Requires centralized interpretation

Different permissions per document type enable nuanced publishing models:

  • Public content with moderated comment discussions between multiple viewers
  • Read-only newsletters with feedback forms
  • Interactive websites with multi-viewer participation and collaboration

Token Validation: Every sync operation validates UCAN signature and expiration

Proof Chain Integrity: Resource tokens chain cryptographically to folder tokens

Permission Isolation: Different documents can have different access levels

Capability Confinement: Viewers can only perform actions explicitly granted in token

Offline Verification: UCAN validation works without network access to permission server

  1. Intelligent Sync: Viewers automatically know how to sync each document type
  2. Decentralized Access Control: No centralized permission server needed
  3. Flexible Publishing Models: Mix read-only content with interactive features
  4. Audit Trails: Proof chains provide cryptographic audit of delegation
  5. Offline Operation: Permission validation works without network connectivity

The UCAN token architecture’s flexibility enables interesting future possibilities like viewer-owned documents and interactive bidirectional workflows.

See Future Concepts for exploratory ideas about how UCAN could enable new sovereign application models.