{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreie4qnkcxtzcr67rj3vgjx3wkai6onrqnl3dpt43ww6wemc6z7c7xm",
"commit": {
"cid": "bafyreiduhla2fiqsn3iqhqlqi6gs7ntgvr52zg64h7etjh24c5uzkeo6aa",
"rev": "3mogc44fbbm22"
},
"uri": "at://did:plc:dadhhalkfcq3gucaq25hjqon/app.bsky.feed.post/3mogc44baek25",
"validationStatus": "valid"
},
"content": {
"$type": "pub.leaflet.content",
"pages": [
{
"$type": "pub.leaflet.pages.linearDocument",
"blocks": [
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Previously on Journey to a distributed PDS, I discovered that when one node generates the DPoP nonce that a client uses as part of a request and another node then receives that as part of a request to validate, it would fail because node one would have generated it differently than node 2. This was due to Cocoon using an incremental counter on a timer which would get used to generate and then validate a nonce. Each node would have a different counter and so they would both calculate different values."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I had a couple ideas on how to solve this, but wasn't really feeling them and thankfully Devin suggested a really good idea."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.bskyPost",
"clientHost": "bsky.app",
"postRef": {
"cid": "bafyreibsyijztic6t3xeqwhzhovx3k4m366jqymxbi4zcsy3a5ogv245vm",
"uri": "at://did:plc:l3rouwludahu3ui3bt66mfvj/app.bsky.feed.post/3mmajpjicak25"
}
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Once I saw this I knew immediately that this would probably be the best solution and I was annoyed that I didn't think of it myself. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "So off I went to implement it and it turned out to be pretty damn easy to do and it worked perfectly."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.iframe",
"height": 360,
"url": "https://tangled.org/willdot.net/distributed-pds/pulls/4/round/0"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "There's the PR that I used and it's relatively straight forward. "
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Some things to note are:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.unorderedList",
"children": [
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "A new env which contains the nonce secret (so that all nodes can generate the same nonce). Previously this was just a randomly generated value that was then stored to a local file so that it was consistent across restarts. No harm in making it an env."
}
},
{
"$type": "pub.leaflet.blocks.unorderedList#listItem",
"content": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "A new nonce generator that instead of using an incremental counter, used a rounded time value. Currently it's hardcoded to 15 minute intervals because I need to do some testing on how small I can make that interval. The smaller the interval, the less time that a nonce value is valid for which tightens up the secureness of the value."
}
}
]
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": ""
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I've been using it like this for a few weeks and haven't had any issues so far. The past few days I've attempted to test with a small interval and managed to get it down to 1 minute which is what Cocoon was using for it's rotation before, so I expect I'll PR that soon."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "I was really impressed how this solution turned out because I was actually dreading getting into the OAuth trenches to figure it out."
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"plaintext": "Side note:"
}
},
{
"$type": "pub.leaflet.pages.linearDocument#block",
"block": {
"$type": "pub.leaflet.blocks.text",
"facets": [
{
"features": [
{
"$type": "pub.leaflet.richtext.facet#link",
"uri": "https://tangled.org/willdot.net/distributed-pds"
}
],
"index": {
"byteEnd": 60,
"byteStart": 45
}
}
],
"plaintext": "I created a new fork of Cocoon and called it distributed-pds because I wanted to have everything all in one place and not clutter up the fork I had for Cocoon which I like to contribute from time to time. Feel free to give it a star and follow the progress. I'm currently in the middle of adding some documentation on how to run it in distributed mode."
}
}
],
"id": "019ed14e-c8d4-7111-a677-d917c9718e21"
}
]
},
"description": "",
"path": "/3mogc3tvd4s25",
"publishedAt": "2026-06-16T17:10:00.790Z",
"site": "at://did:plc:dadhhalkfcq3gucaq25hjqon/site.standard.publication/3lzrigh3cl22p",
"tags": [],
"title": "Journey to a distributed PDS: TOTP auth"
}