ActivityPub is a standard for publishing structured social network data on the Web in JSON-LD format. This document describes various methods for discovering the ActivityPub object described by an HTML page, and conversely the HTML page for an ActivityPub object.
This is a draft of the Social Web Incubator Group (SocialCG) Discovery Task Force.
ActivityPub is a standard for publishing structured social network data on the Web in JSON-LD format and sharing that data from client to server and from server to server. This document describes several methods for discovering the ActivityPub object described by an HTML page, and conversely the HTML page for an ActivityPub object.
Social data in the ActivityPub model is a "resource" like a person, image, or place. That resource has an ActivityPub JSON-LD representation (if it doesn't, it's not covered by this document!) and may have an HTML representation. The ActivityPub representation and the HTML representations each have an URL -- possibly the same URL.
"Forward discovery" is any process that, given the resource's HTML representation, will return the resource's ActivityPub representation.
There are multiple techniques for forward discovery which will be documented in this report. Some discovery techniques require the full HTML document -- its markup and content. Others only need the URL of the HTML representation.
When a consumer is doing discovery, it can start with the input it has -- document or URL -- and convert to the other input if it needs to.Converting an HTML document to an HTML URL usually requires access to the document's context, like a browser environment. Converting an HTML URL to a document requires fetching the document and parsing it.
"Reverse discovery" is any process that, given the resource's ActivityPub representation, will return the resource's HTML representation.
As with forward discovery, some reverse discovery techniques require the full ActivityPub document -- its JSON properties and content. Others only need the URL of the ActivityPub representation.
When a consumer is doing reverse discovery, it can start with techniques that use the input it has -- document or URL -- and switch to techniques that use the other input only if needed. Converting an ActivityPub document to an ActivityPub URL requires extracting the id
property of the object. Converting an ActivityPub URL to a document requires fetching the document and parsing it.
Some resources have a relationship to another resource, which is its author or creator. The author resource has an ActivityPub JSON-LD representation (again, if it doesn't, it's not covered by this document!) and may have an HTML representation.
"Author discovery" is any process that, given the resource's HTML representation, will return the author resource's ActivityPub representation. Author discovery is important because many ActivityPub processes require delivering activities to the author.
There are a few paths for author discovery:
Note that a resource may have an author resource with an ActivityPub representation, but not have its own ActivityPub representation. An example is an article published in a content-management system (CMS) that is ascribed to an actor with an ActivityPub account who wants to receive credit and/or feedback for the work.
As with forward and reverse discovery, the consumer may start with the URL of the resource HTML or the content of the resource HTML.
For some kinds of resources, especially ActivityPub actors, a Webfinger is a more readable alternative to the ActivityPub representation's URL. In these cases, the forward discovery process and author discovery process might include the Webfinger discovery process as an intermediate step:
In this document, the terms "publisher" and "consumer" are used as in Activity Streams 2.0 Core. The terms are extended to include implementations that publish or consume HTML representations of resources with ActivityPub representations.
In this document, we describe several methods of forward discovery, reverse discovery, and author discovery. Different methods are implemented by different publishers and consumers, and have different trade-offs in terms of complexity, performance, and reliability. A section on verification explains how to verify that the discovered information is accurate. The final sections define best practices for publishers and consumers to maximize interoperability and minimize development effort.
Unless otherwise specified, the techniques described below can be used with any Activity Streams 2.0 types. The best-defined groups of AS2 types for HTML discovery are actor types:
Person
Application
Service
Group
Organization
and digital content types:
Note
Article
Image
Video
Audio
Document
Page
Other ActivityPub types are less likely to have their own HTML representations, such as activity types.
Collection
types are often better represented by an object they are closely related to. For example, an actor's outbox
collection is often provided on the actor's profile page, which is a representation of the actor. Similarly, the likes
or replies
of an Image
object are often provided on the object's page, and don't have an independent HTML representation. That said, this document does not preclude the possibility of HTML representations for collection types.
These are some of the user stories that motivate this work.
Like
the contents, so that I can share it with my followers, let the author know I appreciated it, and save it to my liked
collection. A browser-based ActivityPub API client could submit a Like
activity to the user's ActivityPub server, but it would need to know the ID of the ActivityPub equivalent of the page.Announce
the contents, so that I can share it with my followers. A browser-based ActivityPub API client could submit a Announce
activity to the user's ActivityPub server, but it would need to know the ID of the ActivityPub equivalent of the page.Follow
the actor, so that I can get updates about their activities in my inbox. A browser-based ActivityPub API client could submit a Follow
activity to the user's ActivityPub server, but it would need to know the actor ID of the actor whose profile is being viewed.Note
activity to the user's ActivityPub server, with the profile actor's ID in the to
property, but the API client would need to know the actor ID of the actor whose profile is being viewed.Note
activity to the user's ActivityPub server, with the image's author's actor ID in the to
property, but the API client would need to know the actor ID of the author of the image.https://html.example/blog/page-1.html
, an ActivityPub API client could discover the related ActivityPub ID https://ap.example/api/page-1.jsonld
, retrieve it with machine-readable metadata, and provide affordances for interacting with the object, such as liking, sharing, or replying.content
is to the actor's profile page, it's necessary to be able to turn that link into an actor ID to allow more inspection of the actor and affordances like following or blocking.This document uses a consistent format for example URLs:
https://{name}.example/{path}/{type}-{ordinal}{?ext}
Where:
{name}
is the domain name of the server. There are three default domain names used:
ap.example
- A server that primarily provides ActivityPub JSON-LD documents.html.example
- A server that primarily provides HTML documents.mixed.example
- A server that provides both ActivityPub JSON-LD and HTML documents.{path}
is the path to the object. It should be opaque; none of the paths in this document have
semantic meaning unless otherwise specified.{type}
is the content type of the resource. This will usually be the lowercase version of the
ActivityPub object type, such as Note
, Person
, or Image
.{ordinal}
is an ordinal number, when multiple objects are being described in the same
discussion.{ext}
is an optional "file extension" that indicates the Internet media type of the resource,
including:
.jsonld
for JSON-LD objects.html
for HTML documents.png
for PNG images, .jpg
for JPEG images, etc..jsonld
extension is not common practice for ActivityPub id
values. It is used in this report to highlight that the URL is for a JSON-LD representation.
The structure used in the examples is merely mnemonic and non-normative. None of the techniques described in this document depend on a particular URL structure, unless otherwise specified.
This form of discovery, forward discovery, will identify an ActivityPub JSON-LD resource based on the HTML representation of the object.
These discovery techniques require an URL as input. Consumers may start with URLs if they are extracting links from RSS feeds or microblogging content, or when converting from other social networking platform content.
Content negotiation is a catch-all term for ways of negotiating the representation of a resource through the HTTP protocol. In this document, it will specifically cover proactive negotiation using the Accept header.
Given the URL for an HTML document, such as https://mixed.example/some/path/to/note-1
, a consumer
could attempt to retrieve the corresponding ActivityPub JSON-LD object using this HTTP request:
GET /some/path/to/note-1 HTTP/1.1 Host: mixed.example Accept: application/activity+json, application/ld+json, application/json
A compliant server may respond with the ActivityPub JSON-LD object in the body of the response:
HTTP/1.1 200 OK Content-Type: application/activity+json { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://mixed.example/some/path/to/note-1", "type": "Article", "content": "This is a note." }
This is typically used when the ActivityPub server and the HTML server are implemented in the same software package. Because this has historically been the case for many implementations, some consumers expect this behavior to be the default.
Alternately, the server may respond with a 308 Permanent Redirect
to indicate the location of the JSON-LD
representation.
HTTP/1.1 308 Permanent Redirect Location: https://mixed.example/different/path/to/note-1.jsonld
If the server does not support content negotiation, it may respond with a 406 Not Acceptable
status
code.
HTTP/1.1 406 Not Acceptable Content-Type: text/plain No representation matching this request could be found.
Less compliant servers may ignore the Accept
header altogether and return the HTML content regardless:
HTTP/1.1 200 OK Content-Type: text/html <html> <head> <title>Note 1</title> </head> <body> <p>This is a note.</p>
A more difficult failure mode to detect arises when the server does not support ActivityPub, but does support content negotiation for another JSON format. Such a server returns a 200 OK
status code with a JSON
object that does not use JSON-LD, or JSON-LD object that does not use the Activity Streams 2.0 vocabulary:
HTTP/1.1 200 OK Content-Type: application/json { "property": "value", "otherProperty": "otherValue" }
The HTTP Link header can be used to indicate an alternative representation of a resource. A consumer can use this header to discover the ActivityPub JSON-LD object for an HTML page.
Given the URL for an HTML document, such as https://html.example/user/test1/article-1
, the consumer can
use a HTTP HEAD
request to get the headers for the resource, which will hopefully include the
Link
header:
HEAD /user/test1/article-1 HTTP/1.1 Host: html.example
A compliant server will respond with the headers for the resource:
HTTP/1.1 200 OK Link: <https://ap.example/api/articles/article-1.jsonld>; rel="alternate"; type="application/activity+json"
The link header with the alternate
relation type, and an ActivityPub-compatible media type, indicates
that the ActivityPub JSON-LD object is available at the linked URL.
This can be a very efficient method of discovery, since the consumer does not need to download the entire HTML document and parse its contents.
Servers may also include the Link
header in the response to a GET
request for the HTML page.
GET /user/test1/article-1 HTTP/1.1 Host: html.example
A compliant server will respond with the headers for the resource:
HTTP/1.1 200 OK Link: <https://ap.example/api/articles/article-1.jsonld>; rel="alternate"; type="application/activity+json" Content-type: text/html <html> <head> ...
Some servers may return the full body of the HTML document in response to a HEAD
request, without
including a Link
header.
HTTP/1.1 200 OK Content-type: text/html <html> <head> ...
Webfinger
is a standard for discovering metadata about a resource identified with an URL. Finding the ActivityPub URL for an actor identified with an acct:
URL is well documented in the ActivityPub and Webfinger report. However, Webfinger can be used to find metadata about other resources, including HTML pages with https:
URLs.
Given an URL for a document, like https://html.example/group-1.html
, a GET request can be made to an URL in the /.well-known/
path of the domain for the URL, as follows:
GET /.well-known/webfinger?resource=https%3A%2F%2Fhtml.example%2Fgroup-1.html HTTP/1.1 Host: html.example
Note that the /.well-known/webfinger
path is fixed and required for Webfinger.
A compliant server will respond with the metadata for the resource:
HTTP/1.1 200 OK Content-Type: application/jrd+json { "subject": "https://html.example/group-1.html", "links": [ { "rel": "alternate", "type": "application/activity+json", "href": "https://ap.example/api/groups/group-1.jsonld" } ] }
Note that unlike other URLs used in the examples in this report, the /.well-known/webfinger
path is fixed and required for Webfinger.
The JRD JSON format includes a number of properties, as defined in the Webfinger RFC 7033. The relevant data structure in this example is the object in the links
array with the rel
property set to alternate
and the type
property set to application/activity+json
, an ActivityPub-compatible media type. The href
property of this link is URL of the ActivityPub equivalent for the HTML page.
Not all Webfinger-aware servers return JRD documents for https
URLs. Others might only return JRD documents for URLs that represent actors, such as registered users.
As with other link-relation-based discovery mechanisms, like the HTTP Link header or the <link> element, a JSON or JSON-LD media in the link's type
property might not indicate an ActivityPub URL, but some other JSON or JSON-LD object.
Alternately, a consumer may start with the full contents of an HTML document, including markup and other content. For example, a browser-based application may have access to the HTML loaded in the browser window. It's also usually possible to extract the URL from the environment -- for example, using the document.location
property in a JavaScript environment. But using the document content for discovery can return the ActivityPub equivalent without the HTTP requests that discovery by URL requires, saving some time and network traffic.
The link element is a metadata element used in the <head>
section of an HTML document. It provides
links for the whole document, using a number of different link relations.
To indicate its equivalent ActivityPub object, the HTML page at https://html.example/watch/video-1.html
could include the following link element:
<!doctype html> <html> <head> <title>Video 1</title> <link rel="alternate" type="application/activity+json" href="https://ap.example/api/descriptors/video-1.jsonld" /> </head> <body> <!-- rest of the page --> </body> </html>
Consumers need to parse the HTML to find the link
element with the alternate
relation and an ActivityPub-compatible media type as type
. This can be slow and complicated.
Some servers may include a link
element with an
alternate
relation and with a JSON type or JSON-LD type that does not link to an ActivityPub resource.
<!doctype html> <html> <head> <title>Video 1</title> <link rel="alternate" type="application/json" href="https://api.example/unrelated/videodescriptor.json" /> </head> <body> <!-- rest of the page --> </body> </html>
The a element is an element used in the <body>
section of an HTML document. It can be used to define relationships with other documents, with the benefit that the link is (usually) visible and clickable by a reader.
To indicate its equivalent ActivityPub object, the HTML page at https://html.example/profiles/person-1.html
could include the following a
element:
<!doctype html> <html> <head> <title>Person 1</title> </head> <body> <a rel="alternate" type="application/activity+json" href="https://ap.example/users/person-1.jsonld" > Actor data for Person 1 </a> <!-- rest of the page --> </body> </html>
Consumers will need to parse the HTML to find the a
element with the alternate
relation and an ActivityPub-compatible media type as type
. This can be even more slow and complicated than with the link
header. The link
header is usually in the first few kilobytes of a document, and will usually be nested only 2 levels below the document in the DOM tree. An a
element may be anywhere in the body
, maybe nested very deep in the tree.
As with the link
element, some servers may include an a
element with an
alternate
relation and with a JSON type or JSON-LD type that does not link to an ActivityPub resource.
In addition, many content management systems allow end users to set
rel
and other properties on a
elements, which may result in false matches. Even more than with other methods, using the a
element for discovery requires reverse discovery for confirmation (see Best practices for consumers).
HTML documents can include JSON-LD data in a <script>
element in the <head>
section of the document. This data can be used to provide metadata about the document, including its equivalent ActivityPub object.
Given a page that shows an image at https://html.example/gallery/image-17.html
, the HTML for the page could look like this:
<!DOCTYPE html> <html lang="en"> <head> <title>Image 17</title> <script type="application/ld+json"> { "@context": "https://www.w3.org/ns/activitystreams", "type": "Image", "id": "https://ap.example/api/images/image-17.jsonld", "url": [ { "type": "Link", "mediaType": "text/html", "href": "https://html.example/gallery/image-17.html" } ] } </script> </head> <body> <h1>Image 17</h1> <p><img src="https://html.example/images/image-17.png"></p> </body> </html>
This embedded JSON-LD specifies that an ActivityPub object with the ID https://ap.example/api/images/image-17.jsonld
exists, and that it has an HTML page url
at https://html.example/gallery/image-17.html
, that is, the current page's URL. This is a roundabout, but clear, way to specify the ActivityPub ID of the current page.
Consumers need to parse the HTML page, and the embedded JSON-LD, to extract the ActivityPub object ID. An advantage to this technique is that other properties of the ActivityPub object can be embedded as well; however, to confirm those properties, the consumer will need to fetch the object from its canonical URL, the ID, anyways.
Complicated structures for the url
property may make it hard to confirm that the object's URL is the same as the current page's.
Embedded JSON-LD is very popular for embedding Schema.org metadata. This can lead to false positives when looking for ActivityPub objects.
Reverse discovery, in this report, means identifying the HTML page that represents the same object as an ActivityPub JSON-LD object. This is necessary for user stories like creating a link to an actor in microsyntax.
These techniques require the ActivityPub object's URL as input. Often, the object's URL is obtained either as a property of another ActivityPub object, or from the id
property of the ActivityPub JSON-LD document.
If these techniques aren't successful, the consumer can use the URL to fetch the ActivityPub JSON-LD document, and then use a reverse discovery technique that takes a document as input.
As with forward discovery, it's possible for the HTML and JSON-LD representations of an object to be found at the same URL.
Given the URL for an ActivityPub object, such as https://mixed.example/some/path/to/note-1
, a consumer
could attempt to retrieve the corresponding HTML resource using this HTTP request:
GET /some/path/to/note-1 HTTP/1.1 Host: mixed.example Accept: text/html
A compliant server may respond with the HTML document in the body of the response:
HTTP/1.1 200 OK Content-Type: text/html <html> <head> <title>Note 1</title> </head> <body> <p>This is a note.</p>
Alternately, the server may respond with a 308 Permanent Redirect
to indicate the location of the HTML
representation.
HTTP/1.1 308 Permanent Redirect Location: https://mixed.example/different/path/to/note-1.html
If the server does not support content negotiation, it may respond with a 406 Not Acceptable
status
code.
HTTP/1.1 406 Not Acceptable Content-Type: text/plain No representation matching this request could be found.
Less compliant servers may ignore the Accept
header altogether and return the JSON-LD content regardless:
HTTP/1.1 200 OK Content-Type: application/activity+json { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://mixed.example/some/path/to/note-1", "type": "Note", "content": "This is a note." }
As with forward discovery, it is possible to use the Link
header to identify an HTML page related to a given ActivityPub JSON-LD resource. A Link
header with the alternate
link relation and a type
equal to text/html
indicates an HTML page representing the same object.
The advantage of this technique is that it does not require downloading and parsing the JSON-LD content of the ActivityPub object. The Link
header has fewer options for formatting than other methods such as the url
property, for example, making it slightly easier for consumers.
Given an ActivityPub JSON-LD object at https://ap.example/some/path/person-1.jsonld
, a consumer could use a HEAD
HTTP request to get the relevant headers for the resource:
HEAD /some/path/person-1.jsonld HTTP/1.1 Host: ap.example
The publisher would respond with the HTTP headers, including a Link
header:
HTTP/1.1 200 OK Content-Type: application/activity+json Link: <https://html.example/profiles/person-1.html>; rel="alternate"; type="text/html"
Some non-compliant HTTP servers will send the full body of the resource in the response to the HEAD
request.
The Webfinger protocol can be used to find an HTML page related to an ActivityPub object in a number of ways.
The consumer can identify the resource for the a Webfinger query in two ways. First, the id
property, usually an https
URL, can be passed as the resource
parameter for the Webfinger query. Alternately, if the ActivityPub object is an actor, an acct
URL in the format acct:username@domain.example
can be constructed using the technique for Webfinger reverse discovery. This acct
URL can be used as the resource
parameter for the Webfinger query.
The publisher can provide a link to the HTML representation of the object in the JRD output of the Webfinger query in at least two ways.
First, the links
property of the output object can contain a link object with a rel
property set to alternate
and the type
property set to text/html
. If such a link exists, its href
property is the URL of the related HTML page.
Second, the links
property of the JRD output object may include an object with a rel
property set to http://webfinger.net/rel/profile-page
. This is defined to be "the main home/profile page that a human should visit when getting info about that webfinger account." (https://webfinger.net/rel/) It is not guaranteed to be HTML, but a type
property can further define that. Per the definition, "it's likely text/html if it's for users."
An advantage of using Webfinger for discovery is that it is widely implemented by ActivityPub publishers to enable using acct
URLs as identities.
Given an ActivityPub Place
object at https://ap.example/geo/place-7.jsonld
, a consumer could use a Webfinger query to find the HTML page for the object:
GET /.well-known/webfinger?resource=https%3A%2F%2Fap.example%2Fgeo%2Fplace-7.jsonld HTTP/1.1 Host: ap.example
Note that the /.well-known/webfinger
path is fixed and required for Webfinger.
The publisher could return the following JRD output:
{ "subject": "https://ap.example/geo/place-7.jsonld", "links": [ { "rel": "alternate", "type": "text/html", "href": "https://html.example/map/nl/ams/17921.html" } ] }
In this example, the links
property of the JRD object contains a single object with a rel
property set to alternate
and a type
property set to text/html
. The href
property of this object is the URL of the HTML page representing the object.
Alternately, given an ActivityPub Person
object at https://ap.example/profiles/person-19.jsonld
, the consumer could construct an acct
URL as acct:person-19@ap.example
and use it as the resource
parameter for the Webfinger query:
GET /.well-known/webfinger?resource=acct%3Aperson-19%40ap.example HTTP/1.1 Host: ap.example
Note that the /.well-known/webfinger
path is fixed and required for Webfinger discovery.
The publisher could return the following JRD output:
{ "subject": "acct:person-19@ap.example", "links": [ { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://html.example/profiles/person-19.html" } ] }
In this output, the http://webfinger.net/rel/profile-page
relationship identifies an HTML page for the Person
object.
Some servers may not return JRD documents for https
URLs. Others might only return JRD documents for URLs that represent actors, such as registered users.
These techniques require the ActivityPub JSON-LD document as the input for the process. The document can be obtained through delivery via the ActivityPub protocol, or through the ActivityPub API, or by other means.
If none of these techniques are successful, the consumer can obtain the URL of the object from the id
property, and then try one or more of the techniques that require an URL.
url
propertyActivityPub objects can have an optional url
property, which "[i]dentifies one or more links to representations of the object." The property is the preferred way to indicate a corresponding HTML page for an ActivityPub object.
As with many Activity Vocabulary properties, this can have several formats:
Link
object. This structure is used to provide additional information about the link, including the mediaType
. For an equivalent HTML representation, the mediaType
property will be "text/html". The href
property of the Link
object is the URL.Link
objects.In this example, the url
property is only a string.
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/some/path/person-1.jsonld", "type": "Person", "name": "Person One", "url": "https://html.example/profile/person-1.html" }
In the next example, the url
property is a full Link
-type object with mediaType
property
equal to "text/html".
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/geo/place-17.jsonld", "type": "Place", "nameMap": { "en": "Berlin" }, "url": { "type": "Link", "mediaType": "text/html", "href": "https://html.example/map/de/ber/ber.html" } }
In this final example, the url
property is an array of Link
-type objects with different mediaType
properties.
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/photos/gallery/image-3.jsonld", "type": "Image", "summary": "Jason and Carol at the lake house", "url": [ { "type": "Link", "mediaType": "text/html", "href": "https://html.example/gallery/3.html" }, { "type": "Link", "mediaType": "image/webp", "href": "https://upload.example/files/08/17/2021/lakehouse.webp" } ] }
url
property failure
When the url
property is only a string, it may not
represent an HTML page. Especially for objects with binary content types, like Image
, Video
, and Audio
, the url
property is often used for the URL of the respective binary representation of the object.
The mediaType
of Link
-type objects in the url
property is not always defined, and when it is defined, it is not always "text/html".
The property is defined only for representations of the current object. However, the Link
-type object can have a link relation property, rel
. Publishers may misuse the url
property to including links that aren't a representation of the object, but instead a related object, like "next" or "author".
Publishers of HTML representations and ActivityPub representations include data or metadata to help with discovery of related representations or resources. This data is a claim that the linked resource really has the relationship stated.
Unfortunately, not all claims are true. Consumers need to verify the claims made by publishers, using the verification techniques described here. Some techniques are direct and can be used with confidence; others are heuristics that provide some level of support to the claims, but are not foolproof.
Verification is a process of confirming that an HTML page and an ActivityPub object represent the same resource. This is necessary to ensure that the publisher of one representation is not falsely connecting two unrelated resources. These forms of verification are valid for both forward and reverse discovery.
The most reliable verification method is two-way discovery. This consists of first doing discovery in one direction, and then doing discovery in the other direction with the results. For example, doing forward discovery from an HTML page to an ActivityPub object, and then doing reverse discovery from the ActivityPub object to, hopefully, the same HTML page.
Given an HTML page with the URL https://html.example/downloads/image-14.html
, the consumer could discover the ActivityPub JSON-LD URL using the Link
header method:
HEAD /downloads/image-14.html HTTP/1.1 Host: html.example
The publisher would respond with the HTTP headers:
HTTP/1.1 200 OK Content-Type: text/html Link: <https://ap.example/api/images/image-14.jsonld>; rel="alternate"; type="application/activity+json"
Then, the consumer could fetch the ActivityPub object at https://ap.example/api/images/image-14.jsonld
and look for the URL of the HTML page in the url
property:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/api/images/image-14.jsonld", "name": "Image 14", "type": "Image", "url": "https://html.example/downloads/image-14.html" }
By comparing the URL in the ActivityPub object with the original HTML page URL, the consumer can confirm that the two representations relate to the same resource.
Two-way discovery can fail if the discovered representation does not have a path back to the origin representation.
Two-way discovery can become complicated if more than one discovered representation is found, which may connect back to more than one original representation. Verifying multiple relationships, and ignoring those that cannot be verified, complicates this process for the consumer.
Another mechanism for verifying a discovery process is to compare the origins of the original representation's URL and the discovered representation's URL. The origin is the combination of the scheme, host, and port of a URL. If the origins are the same, the two representations can be considered related.
Content negotiation without a redirect will always have the same origin, as the URL of the HTML page and the URL of the JSON-LD representation are the same.
Given an HTML page with the URL https://mixed.example/profiles/person-3
, the consumer could discover the ActivityPub JSON-LD URL using the <link> element method:
<link rel="alternate" type="application/activity+json" href="https://mixed.example/api/person/person-3" />
The href
property of the <link> element is the URL of the ActivityPub JSON-LD representation. The origin of the URL of the HTML page is https://mixed.example
, and the origin of the URL of the ActivityPub JSON-LD representation is https://mixed.example
, so the origins match and the discovery is verified.
Same origin verification assumes that a single publisher controls an entire domain. Although this is often true for machine-readable formats like JSON-LD, having multiple publishers in control of parts of a domain is more common for HTML documents. For example, documents with URLs starting with https://html.example/home/user1/
might be created by one user, and those starting with https://html.example/home/user2/
might be created by another. A carefully crafted <link> or other mechanism could be used by one user to link their HTML page to an ActivityPub object created by another.
Same origin verification will give a false negative if the publisher is using different domains for HTML pages and ActivityPub JSON-LD objects. This can happen if ActivityPub features are added on to an existing published Web site, or if the publisher needs to keep the domains separate for implementation reasons. If same origin verification gives a negative result, other methods such as two-way verification should be used.
Another means of verification, or more precisely an excuse for skipping verification, is an allowlist. This is a list of origins or, possibly, other properties of the representation that can be used to confirm trust in the publisher and skip verification.
Assuming that each origin is controlled by a single publisher, if the consumer trusts the publisher, they can skip verification of discovery when the original representation has an URL with that origin.
Given an HTML page with the URL https://html.example/profiles/person-3
, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method:
<script type="application/ld+json"> { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/api/person/person-3", "type": "Person", "name": "Person Three" "url": "https://html.example/profiles/person-3" } </script>
Given that the consumer trusts the publisher of https://html.example
, they can skip verification of the discovery process, and accept https://ap.example/api/person/person-3
as the ActivityPub JSON-LD representation's URL.
Maintaining an allowlist is time-consuming. The number of domains with Web sites is in the hundreds of millions; identifying even a tiny fraction to be trusted takes a lot of human effort.
Depending on allowlists as the only means of verification severely limits the number of domains that can be interacted with.
Verification of author discovery is necessary to ensure that attackers cannot maliciously ascribe content to an actor that did not create it.
Unfortunately, the only current way to fully verify the authorship of an HTML object is by scanning the outbox
property of the actor object. With tens or hundreds of thousands of items in the outbox not unusual, this is a time-consuming process that is subject to possible errors.
outbox
properties in ActivityPub are OrderedCollection
objects, often with OrderedCollectionPage
objects that represent pages of content. Scanning this collection from newest to oldest members, the consumer can look for Create
activities with the url
property set to the HTML representation URL being verified, or for activity objects with the url
property set to the HTML representation being verified.
The consumer has discovered that https://ap.example/user/person-6.jsonld
is the author of the resource represented by the HTML document at https://html.example/blog/article-9.html
. The consumer retrieves the ActivityPub JSON-LD for the person:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/user/person-6.jsonld", "type": "Person", "name": "Person Six", "inbox": "https://ap.example/user/person-6/inbox", "outbox": "https://ap.example/user/person-6/outbox", "following": "https://ap.example/user/person-6/following", "followers": "https://ap.example/user/person-6/followers", "liked": "https://ap.example/user/person-6/liked" }
The consumer then fetches the URL that is the value of the outbox
property:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/user/person-6/outbox", "type": "OrderedCollection", "totalItems": 3803, "first": "https://ap.example/user/person-6/outbox/page/39" }
It fetches the first page of the collection:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://ap.example/user/person-6/outbox/page/39", "type": "OrderedCollectionPage", "partOf": "https://ap.example/user/person-6/outbox", "next": "https://ap.example/user/person-6/outbox/page/40", "items": [ { "type": "Create", "actor": "https://ap.example/user/person-6.jsonld", "object": { "id": "https://ap.example/object/article-10.jsonld", "type": "Article", "name": "Article Ten", "url": "https://html.example/blog/article-10.html" } }, { "type": "Create", "actor": "https://ap.example/user/person-6.jsonld", "object": { "id": "https://ap.example/object/article-9.jsonld", "type": "Article", "name": "Article Nine", "url": "https://html.example/blog/article-9.html" } }, { "type": "Create", "actor": "https://ap.example/user/person-6.jsonld", "object": { "id": "https://ap.example/object/article-8.jsonld", "type": "Article", "name": "Article Eight", "url": "https://html.example/blog/article-8.html" } } ] }
The second object in the items
array is a Create
activity with an object
property with a value that includes an url
property with same value as the HTML URL we are trying to verify. Since the claim from the HTML and the ActivityPub JSON-LD support each other, the relation is verified.
This method is error-prone and depends on fetching hundreds or thousands of pages. Not all pages will include the full representation of the objects in their items
array; some may just include the id
property of each, requiring even more requests.
Some authors may not include a Create
activity for every object they create on the Web. They may use an author discovery process to identify the author, but not include the object in the ActivityPub representation.
This method, or heuristic, assumes that the claims of authorship are mutually supporting if the URLs of the representations have the same origin. The origin of an URL includes its protocol, domain name, and port number. The method assumes that the same entity (like a person or organization) controls all URLs published with this origin, and therefore would not make claims to contradict itself.
Given an HTML page with the URL https://mixed.example/profiles/person-3.html
, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method with the value https://mixed.example/api/person-3.jsonld
. Because both URLs have the origin https://mixed.example
, the consumer will assume that the discovery is verified.
Assuming that the same entity controls creation of all URLs on a server is somewhat risky. For HTML creation, especially, some servers divide up into per-user paths. Other servers allow user-uploaded data, including JSON and HTML.
Consumers may include a list of origins or other properties of the representation that don't require verification. This assumes an externally-established trust relationship.
Given an HTML page with the URL https://html.example/profiles/person-3.html
, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method with the value https://ap.example/api/person-3.jsonld
. If the consumer trusts the publisher of https://html.example
, they can skip verification of the discovery process, and accept https://ap.example/api/person-3.jsonld
as the ActivityPub JSON-LD representation's URL.
Establishing trust relationships out-of-band is labor intensive, and most consumers will only have a small number of trusted domains or other entities.
This section describes some best practices for consumers of HTML and ActivityPub.
Consumers with the HTML representation's URL as input should try these techniques:
Consumers with the HTML document as input should try these techniques:
head
element.If discovery with the URL as input is unsuccessful, fetching the document may provided better information. Similarly, if discovery with the document does not succeed, obtaining the URL and using it for discovery may work.
Consumers with rigourous discovery requirements, like indexers or search engines, can try additional discovery methods like Webfinger or embedded JSON-LD. However, it's unlikely that publishers that haven't implemented one of the above methods would implement more obscure methods.
Consumers with the ActivityPub representation's URL as input should try the following techniques:
Consumers with the ActivityPub document as input should try these techniques:
If these techniques are exhausted, using the other type of input is a good next step.
Other discovery techniques are unlikely to be used by publishers if these are not.
Consumers with the URL of HTML representation as input should start with these techniques:
Consumers with the HTML document as input should try these techniques:
fediverse:creator
, are easy to fetch from the head
of the document.Failing these, the next best option is to discover the ActivityPub representation of the resource, and then use ActivityPub properties to discover the author.
More demanding consumers may want to continue with discovery of the author's HTML profile page, but this can be a complicated process.
Consumers should do verification of forward and reverse discovery.
Two-way discovery is the most reliable way to verify results, but can have resource overhead.
Same-origin verification can be reliable for reverse discovery, but is less so for forward discovery or author discovery.
Allowlists are a last resort when other verification is not possible.
Publishers that want consumers to be able to discover ActivityPub object and their authors should consider these methods.
When publishing an Activity Streams 2.0 JSON-LD object for ActivityPub, publishers should consider these best practices.
Link
object on its own, or an array of Link
objects, as the url
property of the ActivityPub object. Explicitly include the type
property with a value of "text/html". Avoid using multiple links with the same type
, since there's no easy way to distinguish which one is the correct HTML representation of the resource.attributedTo
property, either as an URL or as a JSON object with at least the id
property. For activities, include actor
, and for public keys, include owner
.Link
headers with both "alternate" and "author" relations.Add the "application/activity+json" media type explicitly. Don't include multiple links with the same relation and media type, since there's not a clear algorithm for choosing between them.https:
URLs for actor objects, and preferably for all objects. Include members of the links
array both for the "alternate" relation, with media type "text/html", and the "author" relation, with media type "application/activity+json" and if possible with media type "text/html" as well. For actors, implement the http://webfinger.net/rel/profile-page
relation.When publishing HTML representations of an ActivityPub resource, include these discovery options:
link
element with rel
set to "alternate" and type
set to "application/activity+json". If possible, also include a link element for the author resource, with rel
set to "author" and type
set to "application/activity+json". Avoid having multiple links with the same relation and media type, since there's not an easy way to determine which one is the best.a
elements to the page layout that users can find and click. Add at least one a
element with rel
equal to "alternate" and type
equal to "application/activity+json". Add at least one a
element with rel
equal to "author" and type
equal to "application/activity+json".Don't include multiple a
links with the same rel
and type
values unless they also have the same href
value.Link
headers for discovery. Add one Link
header with rel
set to "alternate" and type
set to "application/activity+json". Add another with rel
set to "author" and type
set to "application/activity+json". Avoid having duplicate Link
headers with the same rel
and type
values.meta
element with name fediverse:creator
and value set to the Webfinger of the author preceded by an "@" symbol.