Implemented authentication and authorization for WebSocket connections in node-solid-server
Summary
Implemented authentication and authorization for WebSocket connections in node-solid-server, enforcing ACL Read permissions before allowing subscriptions. Supports both session cookie and Bearer token authentication methods.
Motivation
WebSocket subscriptions previously lacked authentication and authorization checks, allowing any client to subscribe to resource updates regardless of ACL permissions. This posed a security risk where unauthorized users could monitor changes to private resources.
Changes
1. WebSocket Authorization Infrastructure
Modified node-solid-ws/lib/server.js
- Added
authorize callback option to WebSocket server constructor
- Integrated authorization check before subscription processing
- Returns
err <uri> forbidden when authorization fails
- Returns
ack <uri> on successful authorization
- Backward compatible - if no
authorize callback provided, subscriptions proceed without checks
Modified lib/create-server.mjs - Authorization callback (lines 210-270)
- Created
authorizeSubscription callback function
- Checks ACL Read permission using
ACLChecker.can(userId, 'Read')
- Validates URL length (max 2048 chars) to prevent DoS
- Prevents SSRF via hostname validation
- Supports both authenticated and anonymous requests
2. Session Cookie Authentication for WebSockets
Modified lib/create-server.mjs - Upgrade handler (lines 107-140)
- Extract session middleware from Express app
- Manually parse session cookies during WebSocket upgrade event
- Extract WebID from
request.session.userId
- Store WebID on request object for authorization callback
3. Bearer Token Authentication for WebSockets
Modified lib/create-server.mjs - Upgrade handler (lines 141-198)
- Check
Authorization: Bearer <token> header if no session found
- Integrate OIDC Resource Server (
oidc.rs.authenticate()) for token validation
- Extract WebID from token claims via
oidc.webIdFromClaims()
- Support configurable token types (
DPoP, Bearer)
- Graceful degradation on invalid tokens (treated as anonymous)
4. Async Authentication Pattern
Implementation Details:
- Wrapped authentication logic in
authPromise (async IIFE)
- Made authorization callback async to await authentication completion
- Ensures Bearer token validation finishes before subscription authorization
Authentication Flow:
1. Client connects with session cookie OR Authorization: Bearer <token>
2. Upgrade event fires → authPromise created
- Parse session cookies (if present)
- If no session, check Authorization header
- Call oidc.rs.authenticate() to validate token
- Extract WebID from session or token claims
- Store webId on request object
3. WebSocket connection established
4. Client sends 'sub <uri>' message
5. Authorization callback invoked
- Waits for authPromise to complete
- Checks ACL Read permission for the resource
- Allows or denies subscription
Tests
Added comprehensive test coverage:
node-solid-server Tests (7 tests added)
File: test/integration/authentication-oidc-test.mjs
Session Cookie Authentication (4 tests):
- ✅
should allow authenticated WebSocket subscription to private resource
- ✅
should deny anonymous WebSocket subscription to private resource
- ✅
should allow authenticated subscription to public resource
- ✅
should allow anonymous subscription to public resource
Bearer Token Authentication (3 tests):
- ✅
should allow Bearer token WebSocket subscription to private resource
- ✅
should allow Bearer token WebSocket subscription to public resource
- ✅
should degrade gracefully with invalid Bearer token
Test Results: 872 passing (15s)
node-solid-ws Tests (3 tests added)
File: test/websockets.js
Authorization Callback Suite:
- ✅
should receive ack when authorization allows subscription
- ✅
should receive err when authorization denies subscription
- ✅
should receive err when authorization callback returns error
Test Results: 8 passing (82ms)
Test Infrastructure Changes
Modified: test/resources/accounts-scenario/alice/.acl-override
- Added public Read authorization for root resource
- Allows anonymous WebSocket subscriptions to public resources
- Uses
foaf:Agent agentClass for public access
- Only applies to root (
./), not default for children
Security
ACL Enforcement:
- All WebSocket subscriptions require Read permission
- Authorization checks use existing ACL infrastructure
- Private resources deny anonymous access
- Public resources allow anonymous access
Protection Against Attacks:
- URL length validation prevents DoS attacks
- Hostname validation prevents SSRF
- Invalid tokens cannot elevate to authenticated sessions
- Failed authentication logs errors but doesn't crash server
Compatibility
- ✅ Backward compatible - no breaking changes
- ✅ Works with both authentication methods simultaneously
- ✅ Existing REST API authentication unchanged
- ✅ Public resources remain accessible anonymously
- ✅ No configuration changes required
Dependencies
- Uses existing OIDC infrastructure (
@solid/oidc-rs, @solid/oidc-auth-manager)
- Uses existing ACL checker (
@solid/acl-check)
- Modified
node-solid-ws (v0.4.3) to support authorization callbacks
- For local development: Use
npm link solid-ws to test with modified node-solid-ws
Files Modified
node-solid-server:
lib/create-server.mjs - WebSocket authentication and authorization
test/integration/authentication-oidc-test.mjs - WebSocket auth test suite
test/resources/accounts-scenario/alice/.acl-override - Public read for root
node-solid-ws:
lib/server.js - Authorization callback support
test/websockets.js - Authorization callback test suite
Configuration
- Authorization is automatically enabled when
live: true is set
- Bearer token support automatically enabled when OIDC is configured
- Token types configurable via
ldp.tokenTypesSupported (defaults to ['DPoP', 'Bearer'])