Your Data, Your Server

Access Control in 5 Minutes with the JavaScript Solid Server
April 2026 · melvin.me · Part 1 of Solid Articles

1. The Problem

Your data lives on someone else's computer. Your notes are on Google's servers. Your photos are on Meta's servers. Your messages are on Apple's servers. You have an account on each one, and each one decides what you can do with your own data.

The Solid project, started by Tim Berners-Lee, inverts this. Your data lives on your server, in your pod. Apps come to the data. You control who sees what.

The access control layer is called Web Access Control (WAC). It's a W3C standard. It's simple. And you can have it running on your machine in five minutes.

2. Install

The JavaScript Solid Server (JSS) is a minimal Solid server. 18K lines of code, 15 dependencies, runs anywhere Node.js runs — including Android phones.

npm install -g javascript-solid-server
jss start

That's it. Server running on http://localhost:4443.

3. Create a Pod

A pod is your personal data space. Create one:

curl -X POST http://localhost:4443/.pods \
  -H "Content-Type: application/json" \
  -d '{"name": "alice"}'

Response:

{
  "name": "alice",
  "webId": "http://localhost:4443/alice/profile/card#me",
  "podUri": "http://localhost:4443/alice/",
  "token": "eyJ..."
}

Three things happened:

A WebID was created. http://localhost:4443/alice/profile/card#me is Alice's identity on the web. It's a URL that resolves to a profile document — other people and apps can look it up to find out who Alice is.

A token was issued. This is a signed JWT that proves you're Alice. Include it in requests with Authorization: Bearer eyJ... to write to your pod. Tokens expire after an hour — for persistent access, enable the built-in identity provider with jss start --idp.

Files were written to disk. JSS stores everything as plain files. Your pod lives in the ./data/ directory, relative to wherever you ran jss start:

data/alice/
├── .acl                    # Who can access this pod
├── profile/
│   └── card                # WebID profile (HTML + JSON-LD)
├── public/                 # World-readable files
├── private/                # Owner-only files
│   └── .acl
├── inbox/                  # Anyone can drop messages here
│   └── .acl
└── settings/               # Preferences (owner-only)
    └── .acl

No database. No proprietary format. Just directories and files you can ls, cat, back up with rsync, or version with git. The .acl files control who can read and write — we'll get to those in a moment.

Save that token — it's your key. Here's what each folder does:

PathPurposeDefault Access
/alice/Pod rootPublic read
/alice/public/Shared filesPublic read
/alice/private/Your filesOwner only
/alice/inbox/MessagesPublic append
/alice/profile/cardYour identityPublic read

4. Write a Resource

First, save the token from the pod creation response so you can use it in commands:

TOKEN="eyJ..."  # paste your token here

Now put something in your pod. JSS speaks JSON-LD — JSON with meaning:

curl -X PUT http://localhost:4443/alice/public/note.json \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/ld+json" \
  -d '{
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "Hello World",
    "author": "Alice",
    "datePublished": "2026-04-02"
  }'

Read it back — no authentication needed, it's in /public/:

curl http://localhost:4443/alice/public/note.json
What just happened

You wrote a linked data document to your pod. Anyone can read it. The data is yours, stored on your machine, in a standard format that any Solid app can understand.

5. How ACLs Work

Every resource can have an .acl file that controls who can do what. The ACL is itself a JSON-LD document. The model is simple:

ConceptWhat It Means
acl:agentA specific person (by WebID)
acl:agentClass foaf:AgentEveryone (public)
acl:agentClass acl:AuthenticatedAgentAnyone who's logged in
acl:accessToWhich resource this rule applies to
acl:defaultApply to everything inside this container
acl:mode ReadCan read
acl:mode WriteCan create, update, delete
acl:mode AppendCan add but not modify or delete
acl:mode ControlCan change the ACL itself

ACLs are inherited. If a resource doesn't have its own .acl, the server walks up the directory tree until it finds one. The acl:default keyword means "apply this rule to everything below."

6. Make Something Private

Let's write a secret document and lock it down:

curl -X PUT http://localhost:4443/alice/private/secret.json \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/ld+json" \
  -d '{"@type": "Note", "content": "My private thoughts"}'

Alice can read it with her token:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:4443/alice/private/secret.json
# {"@type":"Note","content":"My private thoughts"}

But without a token, it's locked:

curl http://localhost:4443/alice/private/secret.json
# {"error":"Unauthorized","message":"Authentication required"}

401. The /private/ folder's ACL only allows the pod owner. Let's look at it:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:4443/alice/private/.acl

The ACL says: only Alice (identified by her WebID) can read or write here. Everyone else is denied.

7. Grant Access to Someone Else

Say Bob has a pod too. His WebID is http://localhost:4443/bob/profile/card#me. You want to let him read your secret note, but nobody else.

Create a resource-specific ACL:

curl -X PUT http://localhost:4443/alice/private/secret.json.acl \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/ld+json" \
  -d '[
    {
      "@context": {"acl": "http://www.w3.org/ns/auth/acl#"},
      "@id": "#owner",
      "@type": "acl:Authorization",
      "acl:agent": {"@id": "http://localhost:4443/alice/profile/card#me"},
      "acl:accessTo": {"@id": "http://localhost:4443/alice/private/secret.json"},
      "acl:mode": [{"@id": "acl:Read"}, {"@id": "acl:Write"}, {"@id": "acl:Control"}]
    },
    {
      "@context": {"acl": "http://www.w3.org/ns/auth/acl#"},
      "@id": "#bob",
      "@type": "acl:Authorization",
      "acl:agent": {"@id": "http://localhost:4443/bob/profile/card#me"},
      "acl:accessTo": {"@id": "http://localhost:4443/alice/private/secret.json"},
      "acl:mode": [{"@id": "acl:Read"}]
    }
  ]'

Now test it:

WhoRequestResult
Nobodycurl .../secret.json401 Unauthorized
Alicecurl -H "Auth: Bearer ALICE_TOKEN" .../secret.json200 OK
Bobcurl -H "Auth: Bearer BOB_TOKEN" .../secret.json200 OK (read only)
Charliecurl -H "Auth: Bearer CHARLIE_TOKEN" .../secret.json403 Forbidden
How it works

The .acl file sits next to the resource. It's just data — JSON-LD that the server reads and enforces. No admin panel, no dashboard, no proprietary API. You control access by writing a file.

If the resource doesn't have its own .acl, the server inherits from the parent container's .acl via acl:default. This means one ACL can protect an entire folder.

8. The Full Picture

In five minutes you have:

WhatHow
A personal data servernpm install -g javascript-solid-server
A pod with identityPOST /.pods
Public resourcesWrite to /public/
Private resourcesWrite to /private/
Fine-grained access control.acl files — per resource or per folder
Standard linked dataJSON-LD in, JSON-LD out

No vendor lock-in. No proprietary formats. The same pod works with any Solid app — SolidOS, LOSOS, or anything that speaks HTTP and understands JSON-LD.

JSS also supports five authentication methods, WebRTC peer-to-peer, ActivityPub federation, a Nostr relay, and remoteStorage — all from the same 1MB package.