Space Proxy
In order to let developers start experimenting with the recently published ATProto permissioned data proposal, Habitat is offering a PDS wrapper that implements the proposal. When apps sign in to user PDSs through Habitat, standardized PDS endpoints will be forwarded directly to the PDS. Spaces endpoints are handled by us and repos are maintained centrally. Once permissioned data is finalized and implemented, we'll migrate these repos to the real PDSs.
Start with Getting started to use Habitat for identity resolution. Habitat overrides the resolved DID's PDS endpoint so that auth and requests work as normal from there. Your app OAuth's with Habitat using the standard atproto OAuth protocol and we OAuth with the user PDS.
How spaces work
Make sure to reference the permissioned data proposal for more details. Here's a high-level overview:
A space is an authorization and sync boundary for a set of permissioned records, identified by an (authority, type, skey) triple. Records inside a space are not public and never appear in a user's public repo or on the firehose.
- Each member of a space writes to their own permissioned repo scoped to that space. A space is the union of its members' repos, not one shared document.
- The space authority (host) decides who may read the space, enumerates the writer set, and routes write notifications.
- A repo's state is summarized by a signed commit over an LtHash — an order-independent, incrementally updatable hash — so a syncer can fold in ops and verify the result without replaying history. Signatures are deliberately deniable: the signature covers only random per-commit bytes, and a MAC binds the state digest to them.
How Habitat's implementation differs
Habitat makes some deviations to the proposal to get things working in the proxy arrangment.
Endpoint NSIDs
Once the proposal is finalized, the spaces endpoints are expected to live under the com.atproto.* namespace like other PDS endpoints. However, since Habitat is using the WIP spec and also because our semantics are slightly different (as seen below), we're namespacing the new spaces endpoints under the network.habitat.* namespace. This will also make migrating to the finalized APIs more clear in your app.
Simple Spaces
The proposal calls for a simplespace protocol implemented by all PDSs. Habitat plans on offering our own space management protocol that will be compatible with simple spaces. For now we offer a limited version of the protocol that just implements the member list. We'll bring client attestation, managing app, and public spaces support in upcoming releases. Another key difference is that all spaces created using the Habitat's simplespace APIs will be owned by Habitat so that we can manage the ACL ourselves. The creating user still has "owner" level permissions over the space but the space authority will be did:web:pear.habitat.network. This shouldn't impact applications except that syncing the space will point to Habitat instead of the space owner.
Signing
Since Habitat doesn't have access to a user's private key, we can't sign commits and token using it. Instead we use the did:web:pear.habitat.network#habitat verification method. The intention of the space proxy is to get developers thinking about how to use spaces to organize permissioned data. We suggest skipping verification steps for now since the spaces are centrally managed anyway. However, for those curious, here are the details of Habitat signatures:
Commits
The signature ctx matches the proposal protocol exactly. The only deviation is which key does the signing. A verifier can just hard code the key as did:web:pear.habitat.network#habitat when verifying commit signatures.
Tokens
The delegation tokens should be signed by the user's private key so that the space authority can verify their access before granting space credentials. Since Habitat is handling space.getDelegationToken we just use Habitat's private key to sign the token. The proposal requires "kid": "#atproto" in the space delegation JWT. Habitat set "kid": "#habitat" instead. Verifiers (which will mostly just be Habitat's space.getSpaceCredential) case based on kid to verify the token.
The token itself is a compact JWS. Decoded, a delegation token for Alice against a public space looks like:
// header
{
"alg": "ES256K",
"kid": "#habitat",
"typ": "atproto-space-delegation+jwt"
}
// claims
{
"iss": "did:plc:alice",
"sub": "at://did:web:pear.habitat.network/space/network.habitat.group/3lartabcd2s22",
"aud": "did:web:pear.habitat.network#atproto_space_host",
"iat": 1785974400,
"exp": 1785974460,
"jti": "qX3n8Yh2VjKpLmR7sTuAwQ"
}
Note that iss is still Alice since the token asserts her access.
Endpoints lists the full surface method by method along with deviations from the proposal.