Creating Your Own did:web

We're in day 3 of the ongoing Bluesky crashout, which is making my desire to figure out how ATProto works (or doesn't) outside of Bluesky as a company feel more urgent. I remain somewhat sympathetic on both sides here. Tech people saying "you can build your own thing if you don't like it" feels dismissive and generally if you find yourself on Jesse Singal's side of a moderation argument you're probably doing something wrong. On the other hand, the levels of brigading and general nastiness of the userbase are real problems that make the site unpleasant to use. I dunno, some of these things are just intractable "when tech people have to interact with non-tech people" problems that I have no solution for. I think the ecosystem and technical underpinnings that Bluesky as a company is trying to build are cool and promising, but expecting normal users to know or care about that stuff is unrealistic. For better or worse your proof of concept is a Twitter alternative and you get the headaches that come with that. I tend to agree with protocol engineer Bryan Newbold on this stuff (and people like him are a large part of the reason I'm excited about this ecosystem despite Bluesky-as-company's behavior).
Anyway, all this paired with yesterday's astonishment at how well setting up a PDS just works is just making me more eager to figure out what a meaningful alternative built on atproto could look like, especially in an adversarial situation. The biggest risk right now is the fundamental building block of atproto: the did
or decentralized identifier. This is a non-atproto standard for decentralized identity that atproto is built upon: basically an identity protocol that could be hosted anywhere without a registry. Could is doing a lot of work there, though. Most atproto did
s aredid:plc
s, a novel did signing method created by Bluesky and hosted in their centralized PLC registry. This is the obvious attack point in an adversarial scenario: if Bluesky owns and verifies everyone's identity, it's easy for them to take that away. Bluesky plans to move ownership of the PLC directory to a nonpartisan/nonprofit entity, but it hasn't happened yet, and the ecosystem to deal with an adversarial directory migration isn't there yet. Figuring out hosting a PLC directory mirror could be cool but I've only been playing with this ecosystem for three days and I don't want to get out over my skis yet.
In the meantime, the only "blessed" alternative to did:plc
is did:web
, a self-hosted did
alternative that relies on a web domain. This comes with its own set of potential adversarial problems - in general, most of us get our domain names and DNS from third-party providers that could lock you out. This is another set of protocols I'm not exactly fluent in, and that might be worth digging into at some point during this 30 days, but I think I can confidently say there's not a way to fully self-host a did
right now without relying on a third party that could screw you over. This gives me some cause for concern - it would be nice if I could self-sign my did
from a Rasberry Pi or something - but it's kind of a "no ethical consumption" problem. There's always some part of the chain that's out of your control - if it isn't DNS, it's your ISP, or the unethical production of ethernet cables, and so on down the line. Right now I'll settle for creating something that isn't entirely reliant on Bluesky.
I'm following Stavros Kounis's helpful guide and running it on the atproto-specific server I set up yesterday. Getting the built-in Caddyfile from bluesky's pds repo to serve the did.json
file ended up being the most confusing part of this process, but my loss is your gain
I cloned the linked repo in the guide and installed elliptic:
npm i elliptic
Using keys.js
:
import crypto from 'crypto';
import elliptic from 'elliptic';
// Request a 32 byte key
const key = crypto.randomBytes(32).toString("hex");
const ec = new elliptic.ec('secp256k1');
const prv = ec.keyFromPrivate(key,'hex').getPublic();
Run npm run key
to generate our signing keys, making sure to save Key(hex)
somewhere secure.
First we prepare our json structure using the X and Y base64 values created by generating the kys:
{
"kty":"EC",
"crv":"secp256k1",
"x":"$YOUR_BASE64_X_VALUE",
"y":"$YOUR_BASE64_Y_VALUE",
}
And, since we're running the Caddy docker container generated by Bluesky's PDS installer, we update that Caddyfile (located at /pds/caddy/etc/caddy/Caddyfile
) to serve /.well-known/
:
{
email {YOUR_EMAIL}
on_demand_tls {
ask http://localhost:3000/tls-check
}
}
*.your.domain.here, your.domain.here {
tls {
on_demand
}
# Handle the specific route for did.json
@did_path expression {path} =='/.well-known/did.json'
handle @did_path {
# Rewrite the requested path to point to the actual filename.
rewrite * /did.json
# Set the root directory where the file is located.
root * /etc/caddy
# Serve the (now rewritten) file path.
file_server
}
# Handle all other requests
handle {
reverse_proxy http://localhost:3000
}
reverse_proxy http://localhost:3000
}
Now we can create our did.json
file in /pds/caddy/etc/caddy
so it can be served directly. I'll put my own generated values in here as an example: obviously, replace it with your relevant domain name and generated public key.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:web:atproto.bront.rodeo",
"verificationMethod": [
{
"id": "did:web:atproto.bront.rodeo#owner",
"type": "JsonWebKey2020",
"controller": "did:web:atproto.bront.rodeo",
"publicKeyJwk": {
"kty":"EC",
"crv":"secp256k1",
"x":"GACOFIiKuiqicquhJ0Y493N+FsikDKkSTFNA4LB6r5A=",
"y":"F+OoOwCJ/jqvB8FcIgQuQ2yGkyD+8RsURA3Dyr29/+U="
}
}
],
"authentication": [
"did:web:atproto.bront.rodeo#owner"
],
"assertionMethod": [
"did:web:atproto.bront.rodeo#owner"
]
}
And restart our docker containers, from inside /pds/
:
docker compose up -d
You should now be able to access this json file at {your_domain_name}/.well-known/did.json
:

Cool, I'm serving an (untested) did:web
! From here, ideally, I'd register an account with this did
on the pds
, but it's not immediately clear how to do this with the provided pdsadmin
tool, and I got waylaid by debugging my Caddyfile for a while, so I'll try and figure that out tomorrow. In the meantime I have a did:web that I have no idea what to do with. 'Til next time!