Skip to main content

Access Control

Solid uses Access Control to determine who can read, write, and manage resources. SolidOS supports both Web Access Control (WAC) and Access Control Policy (ACP).

Web Access Control (WAC)

WAC is the original Solid access control system using .acl files.

ACL Modes

ModeDescription
acl:ReadView the resource
acl:WriteModify the resource
acl:AppendAdd to the resource (but not modify existing)
acl:ControlManage the ACL itself

Reading Permissions

import { store, sym } from 'solid-logic'

const ACL = Namespace('http://www.w3.org/ns/auth/acl#')

async function getPermissions(resourceUri) {
const resource = sym(resourceUri)
const aclUri = resource.uri + '.acl'
const acl = sym(aclUri)

try {
await store.fetcher.load(acl)
} catch (e) {
// ACL might not exist, check parent
console.log('No direct ACL, inheriting from parent')
return null
}

const authorizations = store.each(null, ACL('accessTo'), resource, acl.doc())

return authorizations.map(auth => ({
agent: store.any(auth, ACL('agent'), null, acl.doc())?.uri,
agentClass: store.any(auth, ACL('agentClass'), null, acl.doc())?.uri,
modes: store.each(auth, ACL('mode'), null, acl.doc()).map(m => m.uri)
}))
}

Setting Permissions

async function grantAccess(resourceUri, agentWebId, modes) {
const resource = sym(resourceUri)
const aclUri = resource.uri + '.acl'
const acl = sym(aclUri)
const agent = sym(agentWebId)

// Create authorization
const authId = sym(aclUri + '#auth-' + Date.now())

const statements = [
st(authId, RDF('type'), ACL('Authorization'), acl.doc()),
st(authId, ACL('accessTo'), resource, acl.doc()),
st(authId, ACL('agent'), agent, acl.doc()),
...modes.map(mode => st(authId, ACL('mode'), ACL(mode), acl.doc()))
]

await store.updater.update([], statements)
}

// Example: Grant read/write access
await grantAccess(
'https://alice.example/document.ttl',
'https://bob.example/profile/card#me',
['Read', 'Write']
)

Public Access

Grant access to everyone:

// Make resource publicly readable
const statements = [
st(authId, ACL('agentClass'), FOAF('Agent'), acl.doc()),
st(authId, ACL('mode'), ACL('Read'), acl.doc())
]

Authenticated Users

Grant access to any logged-in user:

// Allow any authenticated user to read
const statements = [
st(authId, ACL('agentClass'), ACL('AuthenticatedAgent'), acl.doc()),
st(authId, ACL('mode'), ACL('Read'), acl.doc())
]

Group Access

// Grant access to a group
const group = sym('https://alice.example/groups/friends#group')
const statements = [
st(authId, ACL('agentGroup'), group, acl.doc()),
st(authId, ACL('mode'), ACL('Read'), acl.doc())
]

Default Permissions

Default permissions apply to new resources in a container:

// Set default for container
const statements = [
st(authId, ACL('default'), container, acl.doc()),
st(authId, ACL('agent'), agent, acl.doc()),
st(authId, ACL('mode'), ACL('Read'), acl.doc())
]

Using solid-ui

solid-ui provides widgets for access control:

import { widgets } from 'solid-ui'

// Render ACL editor
const aclEditor = widgets.aclControl.ACLControlBox(
resource,
context,
'resource',
store
)
container.appendChild(aclEditor)

Checking Access

Before performing operations, check if user has access:

import { authn } from 'solid-logic'

async function canWrite(resourceUri) {
const resource = sym(resourceUri)

try {
// Try a HEAD request to check permissions
const response = await store.fetcher.webOperation('HEAD', resource.uri)
const wacAllow = response.headers.get('WAC-Allow')

if (wacAllow) {
return wacAllow.includes('write')
}
} catch (e) {
return false
}

return false
}

Trusted Apps

Users can grant apps specific permissions via their profile:

<#me>
acl:trustedApp [
acl:mode acl:Read, acl:Write ;
acl:origin <https://myapp.example>
] .
// Check if current app is trusted
async function isAppTrusted(webId, origin) {
const person = sym(webId)
await store.fetcher.load(person.doc())

const trustedApps = store.each(person, ACL('trustedApp'))
for (const app of trustedApps) {
const appOrigin = store.any(app, ACL('origin'))
if (appOrigin?.uri === origin) {
return store.each(app, ACL('mode')).map(m => m.uri)
}
}
return []
}

Common Patterns

Private by Default

// Only owner has access
const ownerAuth = [
st(authId, ACL('agent'), owner, acl.doc()),
st(authId, ACL('mode'), ACL('Read'), acl.doc()),
st(authId, ACL('mode'), ACL('Write'), acl.doc()),
st(authId, ACL('mode'), ACL('Control'), acl.doc())
]

Share with Specific People

// Owner + specific friends
const friendAuth = [
st(friendAuthId, ACL('agent'), friend1, acl.doc()),
st(friendAuthId, ACL('agent'), friend2, acl.doc()),
st(friendAuthId, ACL('mode'), ACL('Read'), acl.doc())
]

Public Read, Owner Write

// Common blog pattern
const publicRead = [
st(publicAuthId, ACL('agentClass'), FOAF('Agent'), acl.doc()),
st(publicAuthId, ACL('mode'), ACL('Read'), acl.doc())
]

const ownerWrite = [
st(ownerAuthId, ACL('agent'), owner, acl.doc()),
st(ownerAuthId, ACL('mode'), ACL('Read'), acl.doc()),
st(ownerAuthId, ACL('mode'), ACL('Write'), acl.doc()),
st(ownerAuthId, ACL('mode'), ACL('Control'), acl.doc())
]

See Also