The ATP identity system has a number of requirements:
Adopting this system should give applications the tools for end-to-end encryption, signed user data, service sign in, and general interoperation.
We use two interrelated forms of identifiers: the handle and the DID. Handles are DNS names while DIDs are an emerging W3C standard which act as secure & stable IDs.
The following are all valid user identifiers:
@alice.host.com
at://alice.host.com
at://did:plc:bv6ggog3tya2z3vxsub7hnal
The relationship between them can be visualized as:
┌──────────────────┐ ┌───────────────┐
│ DNS name ├──resolves to──→ │ DID │
│ (alice.host.com) │ │ (did:plc:...) │
└──────────────────┘ └─────┬─────────┘
↑ │
│ resolves to
│ │
│ ↓
│ ┌───────────────┐
└───────────references───────┤ DID Document │
│ {"id":"..."} │
└───────────────┘
The DNS handle is a user-facing identifier — it should be shown in UIs and promoted as a way to find users. Applications resolve handles to DIDs and then use the DID as the stable canonical identifier. The DID can then be securely resolved to a DID document which includes public keys and user services.
Handles | Handles are DNS names. They are resolved using the com.atproto.handle.resolve() XRPC method and should be confirmed by a matching entry in the DID document. |
DIDs | DIDs are an emerging W3C standard for providing stable & secure IDs. They are used as stable, canonical IDs of users. |
DID Documents |
DID Documents are standardized objects which are hosted by DID registries. They include the following information:
|
The DID standard supports custom "methods" of publishing and resolving DIDs to the DID Document. A variety of existing methods have been published so we must establish criteria for inclusion in this proposal:
At present, none of the DID methods meet our standards fully. Therefore we have chosen to support did-web and a temporary method we've created called did-placeholder. We expect this situation to evolve as new solutions emerge.
Handles in ATP are domain names which resolve to a DID, which in turn resolves to a DID Document containing the user's signing pubkey and hosting service.
Handle resolution uses the com.atproto.handle.resolve
XRPC method. The method call should be sent to the server identified by the handle, and the handle should be passed as a parameter.
Here is the algorithm in pseudo-typescript:
async function resolveHandle(handle: string) {
const origin = `https://${handle}`
const res = await xrpc(origin, 'com.atproto.handle.resolve', {handle})
assert(typeof res?.did === 'string' && res.did.startsWith('did:'))
return res.did
}
Consider a scenario where a hosting service is using PLC and is providing the handle for the user as a subdomain:
alice.pds.com
did:plc:12345
https://pds.com
At first, all we know is alice.pds.com
, so we call com.atproto.handle.resolve()
on alice.pds.com
. This tells us the DID.
await xrpc.service('https://alice.pds.com').com.atproto.handle.resolve() // => {did: 'did:plc:12345'}
Next we call the PLC resolution method on the returned DID so that we can learn the hosting service's endpoint and the user's key material.
await didPlc.resolve('did:plc:12345') /* => {
id: 'did:plc:12345',
alsoKnownAs: `https://alice.pds.com`,
verificationMethod: [...],
service: [{serviceEndpoint: 'https://pds.com', ...}]
}*/
We can now communicate with https://pds.com
to access Alice's data.
Suppose we have the same scenario as before, except the user has supplied their own domain name:
alice.com
(this differs from before)did:plc:12345
https://pds.com
We call com.atproto.handle.resolve()
on alice.com
to get the DID.
await xrpc.service('https://alice.com').com.atproto.handle.resolve() // => {did: 'did:plc:12345'}
Then we resolve the DID as before:
await didPlc.resolve('did:plc:12345') /* => {
id: 'did:plc:12345',
alsoKnownAs: `https://alice.com`,
verificationMethod: [...],
service: [{serviceEndpoint: 'https://pds.com', ...}]
}*/
We can now communicate with https://pds.com
to access Alice's data. The https://alice.com
endpoint only serves to handle the com.atproto.handle.resolve()
call. The actual userdata lives on pds.com
.
Let's consider a self-hosting scenario. If using did:plc, it would look something like:
alice.com
did:plc:12345
https://alice.com
However, if the self-hoster is confident they will retain ownership of the domain name, they can use did:web instead of did:plc:
alice.com
did:web:alice.com
https://alice.com
We call com.atproto.handle.resolve()
on alice.com
to get the DID.
await xrpc.service('https://alice.com').com.atproto.handle.resolve() // => {did: 'did:web:alice.com'}
We then resolve using did:web:
await didWeb.resolve('did:web:alice.com') /* => {
id: 'did:web:alice.com',
alsoKnownAs: `https://alice.com`,
verificationMethod: [...],
service: [{serviceEndpoint: 'https://alice.com', ...}]
}*/
The AT Protocol will launch soon.
Join the waitlist to try the beta before it's publicly available.