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.
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.
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:
| Path | Purpose | Default Access |
|---|---|---|
/alice/ | Pod root | Public read |
/alice/public/ | Shared files | Public read |
/alice/private/ | Your files | Owner only |
/alice/inbox/ | Messages | Public append |
/alice/profile/card | Your identity | Public read |
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
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.
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:
| Concept | What It Means |
|---|---|
acl:agent | A specific person (by WebID) |
acl:agentClass foaf:Agent | Everyone (public) |
acl:agentClass acl:AuthenticatedAgent | Anyone who's logged in |
acl:accessTo | Which resource this rule applies to |
acl:default | Apply to everything inside this container |
acl:mode Read | Can read |
acl:mode Write | Can create, update, delete |
acl:mode Append | Can add but not modify or delete |
acl:mode Control | Can 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."
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.
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:
| Who | Request | Result |
|---|---|---|
| Nobody | curl .../secret.json | 401 Unauthorized |
| Alice | curl -H "Auth: Bearer ALICE_TOKEN" .../secret.json | 200 OK |
| Bob | curl -H "Auth: Bearer BOB_TOKEN" .../secret.json | 200 OK (read only) |
| Charlie | curl -H "Auth: Bearer CHARLIE_TOKEN" .../secret.json | 403 Forbidden |
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.
In five minutes you have:
| What | How |
|---|---|
| A personal data server | npm install -g javascript-solid-server |
| A pod with identity | POST /.pods |
| Public resources | Write to /public/ |
| Private resources | Write to /private/ |
| Fine-grained access control | .acl files — per resource or per folder |
| Standard linked data | JSON-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.
Part 2: Your Nostr Key Is Your Login. Over a million people have Nostr keys. Those keys can authenticate to your pod — no signup, no password. Same ACL system, different identity. One line in an ACL file bridges two ecosystems.