The activityPub logo, but its written OauthDiscovery as the wordmark

ActivityPub: Client to Server endpoint discovery

I’ve re-started building with ActivityPub’s Client to Server API. So this post is to document some of the challenges and hiccups.

The very first thing a user will want to do is login, right? So, we ask the user for their handle (webfinger: user@domain.tld). With this, our app can try to find info about how to connect to the site.

Ideally we would start by looking to RFC 8414: OAuth 2.0 Authorization Server Metadata. The main point is that once we have a server URL, we can look up the configuration routes via a predictable URL /.well-known/oauth-authorization-server

Alternately, with a webfinger lookup we might find the OpenID-Connect 1.0 issuer discovery link, leading to /.well-known/openid-configuration (thanks to @FenTiger for the tip).

These 2 methods really are ideal as they return a set of routes with which a user can authorize our app to obtain tokens for secure requests.

Otherwise, ActivityPub defines some oauth endpoints on the actor object, which is a start, however at the time of publishing the spec it seams app registration was not yet agreed upon.

Another issue is that some activitypub servers will not allow CORS on Actor objects. There are valid privacy reasons for this, nevertheless it is an additional hurdle we must face.

If we run into CORS on the user/actor, so we could look to the instance actor for additional info. Depending on implementation, we might find this via nodeinfo (FEP-2677), or we might find it via webfinger (FEP-d556).

The nodeinfo route is a bit more tortuous, because fetching example.social/.well-known/nodeinfo we potentially receive a payload with links to version 2.0, and or 2.1. example.social/nodeinfo/2.1. Parsing nodeinfo’s metadata field, we’d look for staffAccounts which would be an array, let’s just take the first one.

The webfinger route would be, adding the domain itself to the resource lookup : example.social/.well-known/webfinger?resource=https://example.social

From there we parse the webfinger links field which is an array, looking for an object whose has rel=”self” and whose type="application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"".

Whew, this object’s href value leads us to the instance actor.

Finally, we arrive at an actor profile, where we will look for the endpoints field, and hope to find:

  • oauthAuthorizationEndpoint
  • oauthTokenEndpoint
  • oauthRegistrationEndpoint*

If implementations aren’t serving an OAuth discovery endpoint RFC8414, and are limiting requests to actor pages, then there is really not much we can do!

*As mentioned earlier there may not even be an app registration endpoint. There are downsides to dynamic client registration (RFC7591), namely giving users control over app permission revocation.

ActivityPub co-author Evan has proposed FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API, which suggests bypassing dynamic client registration, and using public clients similar to this IETF draft by Aaron Parecki.

The only downside here might be the minor inconvenience of having to publish a Client ID Metadata Document, regardless of how mature a client is.

Fediverse reactions

Comments

  1. @dev OIDC Discovery – https://openid.net/specs/openid-connect-discovery-1_0.html – specifies a link from the webfinger response to the OAuth issuer ID of the authentication server.

    This is the way my implementation works.

    If the link isn't present then I fall back on using the hostname from the webfinger ID.

    1. Thanks, I wasn’t aware of the webfinger link, that is a useful discovery mechanism!

  2. @dev
    Not to mention nomadic identity ignorance…

    1. I’m aware of Nomadic Identity, but not sure what you mean?
      Are you referring to support for FEP-ae97 ?
      https://codeberg.org/fediverse/fep/src/branch/main/fep/ae97/fep-ae97.md

  3. @dev Here's a great summary of some of the problems with DCR: https://blog.modelcontextprotocol.io/posts/client_registration/#challenge-1-operational-limitations-of-dynamic-client-registration

    It's written in the context of MCP, but ignore that; the same issues apply to ActivityPub.

    (I'm not convinced by the "no way to tell a client that its ID is invalid" thing. I think the answer to this is to use PAR. The other points all look valid, though.)

Leave a Reply

Your email address will not be published. Required fields are marked *