ADR-0011: DIDs + VCs for principal identity¶
- Status: Accepted
- Date: 2026-04-12
Context¶
Trails capabilities are invoked by principals. Principals can be humans or agents; both need identity. Options:
- Usernames + passwords (or OIDC). Rails-default. Ties identity to a central auth service. Poor for agent-to-agent.
- API keys. Simple; no agent identity semantics; no delegation; leakable.
- mTLS client certs. Strong identity; poor DX; heavyweight for agent-swarm scenarios.
- DIDs (Decentralized Identifiers). W3C standard. Identity-first, issuer-agnostic. DIDs identify principals; DID documents expose keys.
- DIDs + VCs. DIDs for identity; Verifiable Credentials (VCs) for claims about principals (roles, consents, certifications).
Requirements: - Same identity model for humans and agents (principle AP7 in architecture). - No central auth dependency for v1. - Cryptographically verifiable. - Extensible to carry consent, credentials, capabilities. - Compatible with emerging agent-identity work (WIMSE WG).
Decision¶
DIDs are the principal-identifier primitive; VCs are the claim primitive.
- Every principal has a DID. Defaults supported:
did:key(local, no resolver),did:web(HTTP-resolvable). - Framework resolves DIDs to DID documents via pluggable resolvers (
IdentityResolvertrait). - VCs carry claims (consent, role, credentialing) verifiable independently.
- Principals authenticate by presenting a biscuit token (ADR-0010) tied to their DID.
ctx.principalis a DID throughout the framework — never a username, never an email.
DID methods beyond did:key and did:web are pluggable. did:plc (Bluesky), did:ion (Sidetree) are community adapters.
Consequences¶
Positive¶
- Unified identity for humans and agents. No separate user/service-account tables.
- Decentralized. No OIDC provider required; works offline and across orgs.
- Cryptographically grounded. DID document keys directly verify signatures.
- Future-aligned. WIMSE WG and agent-identity standards converge on DID-based models.
- Composable with VCs. Consent, role, and credential claims layer cleanly.
- No central identity provider dependency. OSS framework doesn't mandate infra.
Negative¶
- Unfamiliar to most devs. DIDs have a steep on-ramp. Mitigated by:
- Defaults (
did:key) require zero infra — just a key pair. - Docs + examples show the path from "I want a login" to "here's your DID."
- No central user directory. Apps that need "list all users" need to maintain their own index (via named graph of
ssn:Accountor similar). - DID resolution cost. Resolving
did:webrequires HTTP. Mitigated by TTL-cached resolution in kernel. - Wallet UX is nascent. End-user wallets for holding DIDs and VCs are still immature. Mitigated by ecosystem maturity improving — Trails absorbs wallets as they stabilize.
did:webresolution transport security is mandatory, not optional. The default resolver rejectshttp://DIDs outright.https://DIDs MUST satisfy either (a) certificate pinning against a kernel-configured trust root, or (b) DNSSEC validation with the AD bit set on the.well-known/did.jsonlookup. Plain TLS without one of these checks is treated as unauthenticated and refused. HTTP redirect chains are bounded (max 3) and must remain within the same origin. Absent these controls, a network-position adversary can substitute the DID document and become the principal.- VC verification algorithm allowlist. The VC verifier MUST reject
alg=none, symmetric algorithms (HS*), and any algorithm not in a configured allowlist. Default allowlist: EdDSA, ES256, ES384. Unconstrainedalgfields are a well-known JWT/JOSE pitfall (algorithm-downgrade / key-confusion attacks); the framework closes the hole at the kernel verifier, not at handler discretion. - Key-rotation overlap cap. The overlap window for a biscuit root key (old key still accepted while new key is being rolled) MUST NOT exceed 72 hours. After the overlap window, the old key is purged from the verifier's trust set; in-flight tokens signed by a purged key are rejected with
KeyPurged. The cap keeps a stolen-old-key window bounded; operators who need longer overlap MUST document the exception as a compliance deviation.
Non-consequences¶
- Apps can still bridge to OIDC via an adapter — OIDC identities map to
did:webresolvers at the IdP. - Apps needing username-based UX can store a display-name-to-DID mapping themselves; framework doesn't care.
Revisit conditions¶
- If DID standards fragment severely, stabilize on the two or three methods with strongest adoption and deprecate others.
- If the WIMSE or similar WG produces a profile superseding DID for agent identity, adopt it as a peer or replacement (with migration path).
- If
did:webadoption collapses in favor ofdid:plcordid:ion(cryptographically-rooted methods that don't require HTTP trust assumptions), dropdid:webfrom the default allowlist and keep it only as an explicit opt-in.