ActivityPub: Part 1
Let’s Federate on ActivityPub
ActivityPub is a protocol for decentralized social networks to that allows federation - imagine servers running their own instances of “Twitter” that can speak with each other, across domains.
The screenshot below shows a toots and its replies on Mastodon, a Twitter-like social network that supports ActivityPub with users across different servers:
Supporting users across federated platforms introduces some interesting problems: posting updates, following / unfollowing users.
In this series of blog posts, I will be documenting my journey in building a simple server that federates with ActivityPub servers. It will be a fun journey to figure out how other implementations work and learn about the fun / pain when operating a federated application.
If you are interested in learning more, it might be easier (and faster) to check out existing implementations (next section) or check out activitypub.rocks.
Existing Implementations
Fediverse.party documents a list of decentralized social platforms, some of which are not ActivityPub compatible (eg. the once wildly popular Disapora).
The most popular ActivityPub federated network would be Mastodon, which became popular when ActivityPub received the W3C Recommendation status. You will have to choose which instances of Mastodon to join though. I am @victorneo@mastodon.social if you want to follow me - my toots are automatically posted to my Twitter as well.
Some others include Pleroma, write.as, and MissKey which is very popular with Japanese users.
Implementation
The Guide for new ActivityPub implementators is the best place to start right and what I will follow, and where you should start if you are ever implementing your own. ActivityPub as it has been understood is another good reference with some good references.
Unfortunately, the official test suite has been down for a while and will continue to be for a while, but fortunately a new one is in the works. For testing purposes at the moment, we will attempt to federate with Mastodon and its ActivityPub implementation.
You can follow my progress on this repository: soda-hub/Soda. There’s currently only one or two working API endpoints for testing out how Following APIs work.
For the Python folks reading this, I am using Starlette to build the application to also take this chance to play with a ASGI framework using asyncio on Python 3. It’s been refreshing so far to be able to use asyncio with a Flask-inspired API for building web applications, and with the nice performance benefits that comes with it.
Let’s Begin: Let’s Follow Someone
The official website has a reference image for a really high-level overview of how Federation works for ActivityPub:
Most interactions between servers that Federate would be over the /inbox
endpoint - except for the initial user discovery. Here is a quick sequence of
interactions when I search for a user on a remote server to follow on Mastodon:
- Server A sends a webfinger request to the remote server (Server B) to identify the user
- Server B replies with the user’s information
- Server A shows the user’s information, and thumbnail
- Server A sends a follow request to Server B when a user clicks Follow to the
user’s
/inbox
address - Server B replies with an Follow object to indicate that following is approved
In terms of HTTP endpoints involved in the same flow:
- Server A:
HTTP GET https://serverb.com/.well-known/webfinger?resource=acct:username@serverb.com
- Server A:
HTTP GET https://serverb.com/(some url)/username
with the headerAccept: application/activity+json
- Server B replies with the user’s information
- Server A shows the user’s information, and thumbnail
- Server A sends a follow request to Server B:
HTTP POST https://serverb.com/(some url)/username/inbox
The webfinger request returns the following response from Mastodon when I query for my own account:
{
"subject":"acct:victorneo@mastodon.social",
"aliases":[
"https://mastodon.social/@victorneo",
"https://mastodon.social/users/victorneo"
],
"links":[
{
"rel":"http://webfinger.net/rel/profile-page",
"type":"text/html",
"href":"https://mastodon.social/@victorneo"
},
{
"rel":"self",
"type":"application/activity+json",
"href":"https://mastodon.social/users/victorneo"
},
{
"rel":"http://ostatus.org/schema/1.0/subscribe",
"template":"https://mastodon.social/authorize_interaction?uri={uri}"
}
]
}
After getting this response, any server would know that they can access my
profile information by looking for the link that points to self
and
of the application/activity+json
type.
Querying the endpoint (with HTTP header Accept: application/activity+json
),
you will get the following response:
{ "@context":["https://www.w3.org/ns/activitystreams", ,,,],
"id":"https://mastodon.social/users/victorneo",
"type":"Person",
"following":"https://mastodon.social/users/victorneo/following",
"followers":"https://mastodon.social/users/victorneo/followers",
"inbox":"https://mastodon.social/users/victorneo/inbox",
"outbox":"https://mastodon.social/users/victorneo/outbox",
"featured":"https://mastodon.social/users/victorneo/collections/featured",
"preferredUsername":"victorneo",
"name":"Victor Neo",
"summary":"\u003cp\u003eEngineering at Carousell. Passionate about the Open 🕸️.\u003c/p\u003e",
"url":"https://mastodon.social/@victorneo",
"manuallyApprovesFollowers":false,
"discoverable":true,
"publicKey":{
"id":"https://mastodon.social/users/victorneo#main-key",
"owner":"https://mastodon.social/users/victorneo",
"publicKeyPem":"-----BEGIN PUBLIC KEY-----\...\n-----END PUBLIC KEY-----\n"
},
"tag":[],
"attachment":[],
"endpoints":{
"sharedInbox":"https://mastodon.social/inbox"
},
"icon":{
"type":"Image",
"mediaType":"image/jpeg",
"url":"https://files.mastodon.social/accounts/avatars/000/077/572/original/a79a3b7bf7011af0.jpg"
}
}
Parsing the response, another server can send a Follow request to
https://mastodon.social/users/victorneo/inbox
to be processed.
Key Format
There’s no “official” format when it comes down specifying the public key encoding format that should be used when generated (see this issue).
Mastodon’s mini-tutorial on setting up a compatible ActivityPub implementation indicates that they use a 2048 bits key using OpenSSL in the terminal… which I assume that they use a library internally for.
As I am using Python for building my app, I will be using pyca/cryptography to help me with key generation for my application. You can view the key generation code here, but definitely do not use it without reading the documentation on what it does as (i) I am not a security expert, and (ii) this is not production grade security configuration.
Next Up
In the next series, we will continue with the Follow interactions, and get a
proper /inbox
endpoint up and going.