Summary
Add remoteStorage protocol support to JavaScriptSolidServer, enabling the server to serve both Solid and remoteStorage clients from the same storage backend. This would make JSS the first server to support both major per-user storage protocols.
Difficulty: 45/100
Estimated Effort: 4-6 days
Dependencies: None (can be implemented as optional plugin)
What is remoteStorage?
remoteStorage is an open protocol for per-user storage on the web, predating Solid. It enables "unhosted" web apps where:
- Users own their data
- Apps request scoped access via OAuth
- Data syncs across devices automatically
The protocol is defined in IETF draft-dejong-remotestorage.
Protocol Comparison
| Feature |
Solid (current) |
remoteStorage |
Compatibility |
| Discovery |
WebFinger → WebID |
WebFinger → storage URL |
Different format, same endpoint |
| Authentication |
Solid-OIDC + DPoP |
OAuth 2.0 Bearer (simpler) |
RS is subset |
| HTTP Methods |
GET/PUT/DELETE/PATCH |
GET/PUT/DELETE/HEAD |
RS is subset |
| Directory Listings |
Turtle/JSON-LD (LDP vocab) |
JSON-LD (simple format) |
Transform needed |
| ETags |
✅ Supported |
✅ Required |
Already works |
| Conditional Requests |
If-Match, If-None-Match |
If-Match, If-None-Match |
Already works |
| CORS |
✅ Enabled |
✅ Required |
Already works |
| Access Control |
WAC (ACL files) |
OAuth scopes (category:rw) |
Different model |
| Path Structure |
/{pod}/path/to/file |
/storage/{user}/{module}/ |
Different |
| Public Data |
ACL-based |
/public/ folder |
Map to ACL |
Why Integrate?
- Larger ecosystem - Access to remoteStorage apps (100+ apps listed)
- Simpler onboarding - RS OAuth is simpler than Solid-OIDC for some apps
- Migration path - RS users can migrate to Solid gradually
- Unified storage - One server, two protocols, same data
- Unique positioning - First server supporting both protocols
Architecture
Proposed Plugin Structure
src/
├── remotestorage/
│ ├── index.js # Plugin registration
│ ├── webfinger.js # RS WebFinger format
│ ├── oauth.js # OAuth 2.0 implicit flow
│ ├── storage.js # GET/PUT/DELETE handlers
│ ├── listings.js # Directory listing formatter
│ └── scopes.js # Scope validation & mapping
Request Flow
┌─────────────────────────────────────────────────────────────┐
│ JSS Server │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WebFinger Handler │ │
│ │ /.well-known/webfinger │ │
│ │ │ │
│ │ ?resource=acct:alice@example.com │ │
│ │ │ │ │
│ │ ├─► rel=remotestorage → RS client │ │
│ │ └─► rel=solid → Solid client │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ remoteStorage Routes │ │
│ │ │ │
│ │ GET /rs/oauth/authorize → OAuth dialog │ │
│ │ POST /rs/oauth/token → Token exchange │ │
│ │ GET /storage/{user}/* → Read file/folder │ │
│ │ PUT /storage/{user}/* → Write file │ │
│ │ DELETE /storage/{user}/* → Delete file │ │
│ │ HEAD /storage/{user}/* → Get metadata │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Solid Routes (existing) │ │
│ │ │ │
│ │ /{pod}/* → Solid LDP + WAC │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Shared Storage Backend │ │
│ │ (filesystem.js) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Implementation Plan
Phase 1: WebFinger Extension (10/100)
Add remoteStorage link relation to existing WebFinger response.
Current response:
{
"subject": "acct:alice@example.com",
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"href": "https://example.com/alice/profile/card"
}
]
}
Extended response:
{
"subject": "acct:alice@example.com",
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"href": "https://example.com/alice/profile/card"
},
{
"rel": "http://tools.ietf.org/id/draft-dejong-remotestorage",
"href": "https://example.com/storage/alice",
"properties": {
"http://remotestorage.io/spec/version": "draft-dejong-remotestorage-26",
"http://tools.ietf.org/html/rfc6749#section-4.2": "https://example.com/rs/oauth/authorize",
"http://tools.ietf.org/html/rfc6750#section-2.3": "Bearer"
}
}
]
}
Phase 2: OAuth 2.0 Implicit Flow (25/100)
Implement simplified OAuth for remoteStorage clients.
Authorization endpoint:
GET /rs/oauth/authorize
?client_id=https://app.example.com
&redirect_uri=https://app.example.com/callback
&response_type=token
&scope=photos:rw documents:r
Authorization dialog:
┌─────────────────────────────────────────────┐
│ App "MyPhotos" is requesting access │
│ │
│ ☑ photos - Read and write │
│ ☑ documents - Read only │
│ │
│ [Deny] [Allow Access] │
└─────────────────────────────────────────────┘
Successful redirect:
https://app.example.com/callback#access_token=abc123&token_type=bearer
Scope format:
photos:rw - Read/write access to /storage/{user}/photos/
documents:r - Read-only access to /storage/{user}/documents/
*:rw - Full access (root scope)
Phase 3: Storage API (20/100)
Implement remoteStorage HTTP API.
GET document:
GET /storage/alice/photos/vacation.jpg
Authorization: Bearer abc123
200 OK
Content-Type: image/jpeg
ETag: "a1b2c3"
Content-Length: 12345
<binary data>
GET folder:
GET /storage/alice/photos/
Authorization: Bearer abc123
200 OK
Content-Type: application/ld+json
ETag: "folder-etag"
{
"@context": "http://remotestorage.io/spec/folder-description",
"items": {
"vacation.jpg": {
"ETag": "a1b2c3",
"Content-Type": "image/jpeg",
"Content-Length": 12345
},
"summer/": {
"ETag": "d4e5f6"
}
}
}
PUT document:
PUT /storage/alice/photos/new.jpg
Authorization: Bearer abc123
Content-Type: image/jpeg
If-None-Match: *
<binary data>
201 Created
ETag: "new-etag"
DELETE document:
DELETE /storage/alice/photos/old.jpg
Authorization: Bearer abc123
If-Match: "old-etag"
200 OK
ETag: "new-folder-etag"
Phase 4: Access Control (15/100)
Map OAuth scopes to storage paths.
// src/remotestorage/scopes.js
function validateScope(token, path, method) {
// Parse path: /storage/alice/photos/vacation.jpg
const [, , user, category, ...rest] = path.split('/');
// Check token scopes
const scope = token.scopes.find(s => {
const [cat, perm] = s.split(':');
return (cat === '*' || cat === category) &&
(perm === 'rw' || (perm === 'r' && method === 'GET'));
});
if (!scope) {
return { allowed: false, error: 'Insufficient scope' };
}
return { allowed: true };
}
Public folder handling:
/storage/{user}/public/* - Readable without auth
- Maps to ACL with
acl:agentClass foaf:Agent in Solid terms
Phase 5: Testing & Compliance (15/100)
Run the official RS API Test Suite.
# Run compliance tests
npx rs-api-test-suite https://localhost:3000/storage/testuser
Test categories:
Configuration
// Proposed config additions
{
"remotestorage": {
"enabled": true,
"basePath": "/storage", // RS storage path prefix
"oauthPath": "/rs/oauth", // OAuth endpoints
"publicFolder": "public", // Public folder name
"tokenExpiry": 86400, // Token lifetime (seconds)
"allowedOrigins": ["*"] // CORS origins for RS
}
}
CLI flag:
jss start --remotestorage # Enable RS support
jss start --no-remotestorage # Disable (default?)
Environment variable:
Storage Mapping
Option A: Shared Storage (Recommended)
RS and Solid share the same filesystem:
data/
└── alice/
├── profile/ # Solid WebID profile
├── photos/ # Accessible via both protocols
│ └── vacation.jpg
├── public/ # RS public folder = Solid public
└── .acl # Solid WAC (RS uses OAuth scopes)
Access patterns:
- Solid:
GET /alice/photos/vacation.jpg (with Solid-OIDC)
- RS:
GET /storage/alice/photos/vacation.jpg (with Bearer token)
Option B: Isolated Storage
Separate storage areas:
data/
└── alice/
├── solid/ # Solid-only data
└── rs/ # RS-only data
Pros: Clean separation, no ACL conflicts
Cons: Data duplication, users manage two silos
Recommendation: Option A (shared) with clear documentation on access model differences.
Scope ↔ WAC Mapping
| RS Scope |
Solid WAC Equivalent |
photos:r |
acl:Read on /photos/ |
photos:rw |
acl:Read, acl:Write on /photos/ |
*:r |
acl:Read on root |
*:rw |
acl:Read, acl:Write, acl:Control on root |
| (public folder) |
acl:agentClass foaf:Agent; acl:Read |
Note: RS scopes are simpler than WAC. RS tokens can only access paths within their scopes, regardless of ACL files.
Compatibility Notes
remoteStorage Apps
Popular RS apps that would work:
remoteStorage.js Library
The official remotestorage.js client library should work out of the box once the protocol is implemented.
const rs = new RemoteStorage();
rs.access.claim('photos', 'rw');
rs.connect('alice@example.com');
Difficulty Breakdown
| Component |
Difficulty |
Notes |
| WebFinger extension |
10/100 |
Add link relation to existing handler |
| OAuth implicit flow |
25/100 |
Simpler than Solid-OIDC |
| Storage GET/PUT/DELETE |
15/100 |
Reuse existing storage backend |
| Directory listings |
10/100 |
JSON-LD transformation |
| Scope validation |
15/100 |
Path-based access check |
| HEAD requests |
5/100 |
Trivial |
| Public folder |
10/100 |
Skip auth for /public/ |
| Compliance testing |
15/100 |
RS test suite |
| Documentation |
10/100 |
Usage guide |
| Total |
45/100 |
|
Open Questions
- Default state: Should RS be enabled by default or opt-in?
- Storage mapping: Shared storage (Option A) or isolated (Option B)?
- Token storage: Reuse IdP accounts DB or separate RS tokens?
- Scope persistence: Store granted scopes in accounts DB?
- Revocation UI: Add RS token management to future admin panel?
References
Related Issues
Summary
Add remoteStorage protocol support to JavaScriptSolidServer, enabling the server to serve both Solid and remoteStorage clients from the same storage backend. This would make JSS the first server to support both major per-user storage protocols.
Difficulty: 45/100
Estimated Effort: 4-6 days
Dependencies: None (can be implemented as optional plugin)
What is remoteStorage?
remoteStorage is an open protocol for per-user storage on the web, predating Solid. It enables "unhosted" web apps where:
The protocol is defined in IETF draft-dejong-remotestorage.
Protocol Comparison
/{pod}/path/to/file/storage/{user}/{module}//public/folderWhy Integrate?
Architecture
Proposed Plugin Structure
Request Flow
Implementation Plan
Phase 1: WebFinger Extension (10/100)
Add remoteStorage link relation to existing WebFinger response.
Current response:
{ "subject": "acct:alice@example.com", "links": [ { "rel": "http://webfinger.net/rel/profile-page", "href": "https://example.com/alice/profile/card" } ] }Extended response:
{ "subject": "acct:alice@example.com", "links": [ { "rel": "http://webfinger.net/rel/profile-page", "href": "https://example.com/alice/profile/card" }, { "rel": "http://tools.ietf.org/id/draft-dejong-remotestorage", "href": "https://example.com/storage/alice", "properties": { "http://remotestorage.io/spec/version": "draft-dejong-remotestorage-26", "http://tools.ietf.org/html/rfc6749#section-4.2": "https://example.com/rs/oauth/authorize", "http://tools.ietf.org/html/rfc6750#section-2.3": "Bearer" } } ] }Phase 2: OAuth 2.0 Implicit Flow (25/100)
Implement simplified OAuth for remoteStorage clients.
Authorization endpoint:
Authorization dialog:
Successful redirect:
Scope format:
photos:rw- Read/write access to /storage/{user}/photos/documents:r- Read-only access to /storage/{user}/documents/*:rw- Full access (root scope)Phase 3: Storage API (20/100)
Implement remoteStorage HTTP API.
GET document:
GET folder:
PUT document:
DELETE document:
Phase 4: Access Control (15/100)
Map OAuth scopes to storage paths.
Public folder handling:
/storage/{user}/public/*- Readable without authacl:agentClass foaf:Agentin Solid termsPhase 5: Testing & Compliance (15/100)
Run the official RS API Test Suite.
# Run compliance tests npx rs-api-test-suite https://localhost:3000/storage/testuserTest categories:
Configuration
CLI flag:
Environment variable:
Storage Mapping
Option A: Shared Storage (Recommended)
RS and Solid share the same filesystem:
Access patterns:
GET /alice/photos/vacation.jpg(with Solid-OIDC)GET /storage/alice/photos/vacation.jpg(with Bearer token)Option B: Isolated Storage
Separate storage areas:
Pros: Clean separation, no ACL conflicts
Cons: Data duplication, users manage two silos
Recommendation: Option A (shared) with clear documentation on access model differences.
Scope ↔ WAC Mapping
photos:racl:Readon/photos/photos:rwacl:Read, acl:Writeon/photos/*:racl:Readon root*:rwacl:Read, acl:Write, acl:Controlon rootacl:agentClass foaf:Agent; acl:ReadNote: RS scopes are simpler than WAC. RS tokens can only access paths within their scopes, regardless of ACL files.
Compatibility Notes
remoteStorage Apps
Popular RS apps that would work:
remoteStorage.js Library
The official remotestorage.js client library should work out of the box once the protocol is implemented.
Difficulty Breakdown
Open Questions
References
Related Issues