Permissioned Spaces

Credentials

Space credentials are short-lived JWTs for cross-service access to space data. A member proves their membership to get a grant, exchanges the grant for a credential JWT, then passes it to an external service that needs to read the space's records.

How credentials work

Credential issuance is a two-step process:

Credentials are ES256 JWTs signed with a P-256 keypair unique to each space. The keypair is generated on first credential request and stored encrypted (AES-256-GCM).

Step 1: Get a member grant

The caller must be an authenticated member of the space. The grant is a short-lived token (5 minutes) that proves membership.

const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.getMemberGrant", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
  }),
});
interface GrantResponse {
  grant: string;
  expiresAt: string;
}
const data: GrantResponse = await response.json();

Response:

{
  "grant": "eyJhbGciOiJIUzI1NiJ9...",
  "expiresAt": "2026-05-09T12:05:00Z"
}

Step 2: Get a space credential

Exchange the grant for a space credential JWT. The credential is signed by the space's keypair and has a 4-hour TTL.

const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.getSpaceCredential", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    grant: "eyJhbGciOiJIUzI1NiJ9...",
  }),
});
interface CredentialResponse {
  credential: string;
  expiresAt: string;
}
const data: CredentialResponse = await response.json();

Response:

{
  "credential": "eyJhbGciOiJFUzI1NiJ9...",
  "expiresAt": "2026-05-09T16:00:00Z"
}

Credential claims

The JWT payload contains:

ClaimDescription
issThe space's DID (who signed it)
subThe member's DID (who it was issued to)
spaceThe full ats:// space URI
scopeAccess level (read)
iatIssued at (Unix timestamp)
expExpiry (Unix timestamp)

Using a credential

Pass the credential as a standard Bearer token in the Authorization header. HappyView distinguishes space credentials from other tokens by checking the JWT header's typ field (space_credential).

const response = await fetch(
  "https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=...&collection=...&rkey=...",
  {
    headers: {
      "Authorization": `Bearer ${SPACE_CREDENTIAL}`,
    },
  },
);
const data = await response.json();

No DPoP auth or client key is needed when authenticating via space credential — the credential itself is sufficient. The user's identity comes from the sub claim in the JWT.

HappyView verifies the credential by resolving the issuer's DID document, extracting the signing key, and validating the JWT signature and expiry. If valid, the request is treated as if the credential's sub is a member of the space.

App access control

Before issuing a credential, HappyView checks whether the calling app (identified by its DPoP client key) is allowed to access the space:

  • default_allow mode: any app can get credentials unless it's on the appDenylist
  • default_deny mode: only apps on the appAllowlist can get credentials

If no client key is present in the DPoP claims, the check is skipped (direct user access without an app intermediary).

External credential verification

HappyView can also verify credentials issued by other HappyView instances or space-aware services. When a Bearer space credential is presented, HappyView:

  1. Decodes the JWT without verification to extract the iss (issuer DID)
  2. Resolves the issuer's DID document
  3. Extracts the signing key from the DID doc
  4. Verifies the JWT signature and expiry
  5. Checks that the space claim matches the requested space

A credential issued by one instance can be used to read from another instance that hosts the same space's data.