This document is non-normative.
Authentication is not specified by the [[[ActivityPub]]] standard. In practice, the fediverse mostly uses [[[RFC9421]]] to authenticate server-to-server requests, using a relatively consistent profile. This document describes that profile and usage, recommends best practices, and evaluates their success so far.
[[ActivityPub]] lets people interact on the fediverse without an existing shared trust anchor. That's great! They still need some form of trust model, though, even if the protocol is decentralized. Specifically, they need to authenticate actors who send activities and request objects, and they need to check whether those actors are authorized to send those activities and request those objects.
The (non-normative) ActivityPub primer discusses authentication and authorization in detail, but the standard itself leaves authentication and authorization largely unspecified. It implies that authorization is a "same origin model" (not to be confused with web browsers): an actor can generally only create, update, or delete objects with itself as actor or attributedTo
. Some of this is deferred to [[[activitystreams-core]]].
For authentication, in practice, the fediverse currently uses a custodial trust model. Each user has one or more asymmetric keypairs, and their instance (server) is generally trusted to hold their private keys, serve their public keys, generate signatures, and serve objects and send activities on their behalf. Most servers require SSL for server-to-server HTTP connections in order to authenticate server domains.
ActivityPub suggests [[[RFC9421]]] and Linked Data Signatures as additional authentication mechanisms. ActivityPub inbox delivery POSTs generally include an HTTP Signature from the sending actor, and for inbox forwarding, sometimes also an LD Signature from the original actor. Object and activity GETs often include an HTTP Signature from the requesting actor.
This document describes the fediverse's current usage of HTTP Signatures for ActivityPub server-to-server requests, recommends best practices, and evaluates their success so far.
Those are broad goals, and this report is limited in scope. Here are a number of non-goals that are intentionally not addressed or prioritized here. We also don't expect to update this report on an ongoing basis. It describes a single snapshot in time, when it was published. Other decentralized social protocols have similar trust models and authentication techniques. Almost are all some combination of SSL and/or per-user asymmetric keypairs. Here are a few examples. vpzom's Are We HS2019 Yet? web site faithfully tracks HTTP Signature support in popular fediverse server projects over time. As of 2024-03-16, it covers 14 projects, including 8 of the top 10 projects by total registered users. We also confirmed micro.blog's and the WordPress ActivityPub plugin's HTTP Signature support. Combined with vpzom's survey, these 16 projects together cover 96% of registered users, 95% of MAUs, and 83% of instances in the fediverse. (Based on fedidb.org/software and fediverse.observer/stats.) We've refrained from duplicating vpzom's full contents, but here are a few highlights. All 16 projects support HTTP Signatures, both generating and sending them in outbound requests and validating signatures on incoming requests. Based on this data and more, [[[http-signatures]]] aka cavage-12 is still the most commonly supported version in the fediverse by far. Not many projects support later versions, especially not projects with large user or instance bases. Very few projects share implementation code. PeerTube and Misskey both use @peertube/http-signature, Pleroma and Mobilizon both use pleroma/http_signatures, the rest all use their own implementations. All projects except one support the FEP-e2ce describes a similar standardized profile for HTTP Signatures in ActivityPub and the fediverse. Its authors supported us in writing this report, which we appreciate greatly! FEP-521a describes an alternative way to support multiple keys per actor with FEP-ae97 proposes a way for clients to hold users' keys and attach data integrity proof style signatures to activities.Non-goals
keyId
s.Comparison with other networks
Survey of standards compliance
hs2019
placeholder algorithm in at least some form. This is a good sign that the fediverse is gradually advancing the version(s) of HTTP Signatures it supports, but getting past cavage-12 will take more effort.Related work
Multikey
.
Here's how to sign an ActivityPub server-to-server HTTP request with a cavage-12 HTTP Signature:
Digest
[[RFC3230]] header value. (Most servers should generally only require this for POST requests, so you may be able to omit it for GETs.)
SHA-256=
.Date
, Host
, and Content-Type
header values.(request-target)
pseudo-header.hs2019
as the algorithm. This is a generic placeholder string that defers algorithm detection to the keyId
public key's metadata.Signature
header as described in cavage-12 section 4.Here's how to verify an incoming request's HTTP Signature, as described in cavage-12 section 2.5. If the verification fails, and you require a valid signature for the given request, you should return an HTTP 401 error response.
Signature
header. If that header is not present, the request has no signature.keyId
parameter from the HTTP Signature.keyId
.hs2019
, assume that means rsa-sha256
, as described in [[[#survey-of-standards-compliance]]].
Date
, Host
, and Content-Type
. They may also include Digest
and the (request-target)
pseudo-header with the target URL.SHA-256=
.Digest
header. If they don't match, the signature is invalid, and you can optionally return an informative message in the error response.Date
header to the current time. If they differ significantly, the verification fails. (The standards don't give a concrete time window to use for this comparison. In practice, an hour plus a few minutes buffer in either direction may be a good value, to account for both clock skew and differences in time zone/daylight savings time configuration across systems.)Signature
header.Here's how to find the public key to use to verify a fediverse HTTP Signature:
keyId
parameter. It should be a URL. If it has a fragment, discard it.Key
object, use its controller
or owner
property as the new key id, jump back to step 2, and repeat. (This is necessary to confirm that the owner actually owns and uses this key.)publicKey
property. If there are multiple values, find the one whose id
matches the original keyId
.publicKeyPem
property. This is based on LD Security Vocabulary v1. Use your cryptography library to decode it as a PEM public key. (It may be encoded as PKCS-1, X.509 SPKI, or something else; your library should detect its format automatically.)Note that a newer version of the LD Security Vocabulary (part of Verifiable Credential Data Integrity) removes the publicKey
property. FEP-521a is an alternative that supports key objects anywhere in actors, eg in the assertionMethod
property, but it's not yet widely supported in the fediverse.
Some servers have a feature called authorized fetch, aka secure mode, which requires HTTP Signatures on all HTTP GET requests for objects and activities. This is intended to increase the server's control over who can access its data. From Mastodon's documentation:
As a result, through the authentication mechanism and avoiding re-distribution mechanisms that do not have your server in the loop, it becomes possible to enforce who can and cannot retrieve even public content from your server, e.g. servers whose domains you have blocked.
For public posts and data, servers with authorized fetch enabled generally don't enforce any fine grained access control over the actors whose signatures they require. They usually only reject requests from actors on domains that they've blocked at the server level. (This means that the name authorized fetch is maybe partially a misnomer, and something like signed fetch might be more appropriate.)
For non-public data, eg followers-only or mention-only posts aka direct messages, servers generally do check that GET requests are signed by actors who should have access to that data.
One consequence of [[[#authorized-fetch]]] is that signed fetches can end up in a kind of deadlock or infinite loop. If server a.example fetches https://b.example/bob with https://a.example/alice's signature, and b.example doesn't have alice's public key, it will get it by fetching https://a.example/alice with https://b.example/bob's signature. a.example won't have bob's public key either, so it will again try to fetch https://b.example/bob, and the cycle will continue.
To prevent this, servers with authorized fetch enabled often use an instance actor to sign object fetches. This is generally a "server-level" actor, separate from any normal user, that doesn't require an HTTP Signature to be fetched. This breaks the loop of fetching each actor back and forth to validate their signatures.
Delete
s of actorsDelete
activities that delete actors can have extra complications. The actor object may already be deleted on the source server, so fetching it might return a 410 or 404 error. Or, the Delete
activity may be signed by the remote server's [[[#instance-actor]]] instead of the actor itself.
Here's a process for handling Delete
activities that delete actors:
Delete
.Delete
's object
or its server's [[[#instance-actor]]], discard the Delete
and do not process it.Delete
.HTTP responses are cached in a wide variety of ways across the web. HTTP Signatures in ActivityPub requests can affect the resulting responses, so clients and servers both need to take signatures account when interacting with caches.
The main thing ActivityPub servers need to do is include Signature
in their Vary
header for responses that depend on request signatures, eg if they require [[[#authorized-fetch]]] and would return a 4xx error if a signature is missing or invalid. This prevents valid responses from being cached and returned to other future requests with missing or invalid signatures.
(This is similar to including Content-Type
in the Vary
response header for URLs that can return either user-facing HTML or [[[activitystreams-core]]] JSON, depending on content negotiation.)
One downside of this is that ActivityPub objects from servers that require authorized fetch generally can't be cached. HTTP Signatures include timestamps via the Date
header, and are often generated by different private keys, so they'll almost never be the same across requests. This is an unfortunate side effect, but necessary for servers that want to control access based on the requester's identity.
The HTTP Signatures standard has made a few backward-incompatible changes on its path to becoming a full Proposed Standard, [[RFC9421]]. Many fediverse servers currently handle older versions of the standard and aren't yet compatible with the final version. Here's advice on how to implement HTTP Signatures so as to be compatible with as many different servers as possible.
The primary technique we recommend is double-knocking. First, try generating or verifying an HTTP Signature with one version, ideally (but not necessarily) the latest. If the remote server rejects that signature, eg with an HTTP 401 response, or the incoming signature doesn't verify, try with another version. Repeat until a signature passes or you've tried all supported versions.
(Many fediverse servers do process incoming activities asynchronously, but they generally still verify signatures synchronously, so double knocking is still viable when delivering activities to remote inboxes.)
Here's a list of ways to check for different versions, in descending order:
Signature-Input
HTTP header? This was only added in the later versions of the standard, notably after cavage-12
. The RFC itself advises this in its appendix on backward compatibility. If Signature-Input
is present, and the signature fails, try removing it and using cavage-12
instead.hs2019
placeholder algorithm? This was added in cavage-12
, then removed again in later versions. It's not present in the final proposed standard. If the signature uses hs2019
and fails, try again with rsa-sha256
.(request-target)
, (created)
, or (expires)
pseudo-headers? If so, and the signature fails, try replacing them with the Date
and/or Host
headers.Keys don't always stay the same forever. Changing an actor's key is called key rotation, and can be a good idea to improve or maintain security in a number of situations. If a private key is leaked or compromised, you should immediately stop using it and switch to a new key instead. You can also do this proactively, regularly, in case of a leak or compromise that you haven't discovered yet. Or the key may have been lost, and can't be restored from backup.
There are two common types of key rotation in the fediverse: signed, where you send an Update
activity for the actor with the new key to all followers, and blind, where you don't, for plausible deniability of past activities.
[[[#how-to-verify-a-signature]]] step 10 mentions how to handle key rotation when you're verifying a signature. RFC9421 discusses key rotation briefly in 7.3.2 Key theft and 8.1. Identification through Keys.
The ActivityPub standard briefly mentions authentication (and authorization), but omits specific needs or use cases for them. Over time, two clear uses for authentication in the fediverse have emerged.
The first is proving and verifying that a given user created a given piece of data or performed a given action. This is the classic attribution problem in identity-based networks. (Note that this is separate from authorization or access control, ie determining whether a given user is allowed to access a given piece of data or perform a given action.)
At a baseline level, this works. HTTP Signatures attached to ActivityPub inbox delivery requests can effectively verify the actor - or at least the server - who sent them. However, these signatures are ephemeral. They only authenticate HTTP requests, not long-lived data. We'd often like to authenticate objects and activities outside of inbox delivery requests, eg during inbox forwarding, or after they were initially created. HTTP Signatures can't do this.
Some fediverse projects like Mastodon use LD Signatures 1.0 for those purposes instead, which works, but isn't widely supported. Even Mastodon's support itself is limited and discouraged. From their docs:
Mastodon’s current implementation of LD Signatures is outdated...Furthermore, the LD Signatures specification as a whole has been superseded by [[[vc-data-integrity]]], which is largely incompatible with the earlier LD Signature spec. For this reason, it is not advised to implement support for LD Signatures.
The second use case for authentication is access control, specifically whether to serve an ActivityPub GET request for a given object or activity. Fediverse users and servers routinely block other remote users and servers, and require [[[#authorized-fetch]]] via HTTP Signatures to identify the remote user making the request to determine whether they're blocked.
Fediverse servers prevent interactions from blocked users/servers via the first use case above: they use HTTP Signatures to identify the remote user, then check if they're blocked. This works, more or less.
As for controlling access to non-public data, eg direct messages and followers-only posts, those are only delivered to the intended recipients' servers, which are expected to only serve them to authorized users. This matches the fediverse's server-centric security model.
Otherwise, as a mechanism for controlling access to public data, HTTP Signatures are only minimally effective, if at all. This isn't a criticism as much as an unavoidable reality. Fediverse servers generally serve public data over the web freely, for anyone to see without logging in or fetching via ActivityPub or HTTP Signature. There's no obvious way to serve public data to anonymous, unauthenticated users, and still block access to specific people. Beyond that, we've seen techniques that circumvent authorized fetch by laundering requests from blocked servers with signatures from other "clean" (non-blocked) server domains.
The conclusion seems to be that HTTP Signatures do serve real use cases in the fediverse, to some degree, but not well, and they're not a solid basis for comprehensive authentication or authorization.