Cheshire

Victor’s Writings

10 May 2020

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:

Toot from Mastodon

Example of a toot from Mastodon

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.

Toot from Mastodon

Some Mastodon Instances you can join

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.

Testsuite

Yikes, not a good sign with the official test suite is down. Fortunately, a new one is in the works.

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:

ActivityPub Overview

High-level Overview of 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:

  1. Server A sends a webfinger request to the remote server (Server B) to identify the user
  2. Server B replies with the user’s information
  3. Server A shows the user’s information, and thumbnail
  4. Server A sends a follow request to Server B when a user clicks Follow to the user’s /inbox address
  5. Server B replies with an Follow object to indicate that following is approved

Mastodon User Search

Mastodon User Search UI

In terms of HTTP endpoints involved in the same flow:

  1. Server A: HTTP GET https://serverb.com/.well-known/webfinger?resource=acct:username@serverb.com
  2. Server A: HTTP GET https://serverb.com/(some url)/username with the header Accept: application/activity+json
  3. Server B replies with the user’s information
  4. Server A shows the user’s information, and thumbnail
  5. 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.