Using custom domains as a Fediverse redirect

Use your own domain name as a redirect to an existing account without running your own instance

Why?

Many people already use custom domains for their blog, e-mail, etc.
As the format of e-mail and fediverse accounts is basically identical, it would be ideal to be able to find Fedi users via their “normal” username@domain.tld address:

iOS fediverse client, searching for sarah@kittenlabs.de

Background

When searching for a specific account via the search function in (for example) Mastodon, the server executes a HTTP GET request like this:

"GET /.well-known/webfinger?resource=acct:sarah@kittenlabs.de HTTP/1.1" 404 11140 "-" "http.rb/5.1.1 (Mastodon/4.2.8; +https://chaos.social/)"

This mechanism is called WebFinger. This spec was extended with an ActivityPub-specific extension.

When this request is sent to a real ActivityPub instance, the server responds like this:

{
    "subject": "acct:manawyrm@chaos.social",
    "aliases": [
        "https://chaos.social/@manawyrm",
        "https://chaos.social/users/manawyrm"
    ],
    "links": [
        {
            "rel": "http://webfinger.net/rel/profile-page",
            "type": "text/html",
            "href": "https://chaos.social/@manawyrm"
        },
        [....]
    ]
}

Instead of hosting our own instance, we can just use simple HTTP 302 redirects for this URL to make our existing accounts discoverable from our custom domain. This does not create an alias, so our account will still be called account@instance.tld, not account@customdomain.tld.

Simple setup (one account for the whole domain)

If you only have one Fediverse account for the whole domain name, you can use a hack and just redirect all accesses to “/.well-known/webfinger” to your own instance.

Example Apache2 webserver configuration (can be applied in .htaccess or the server config):

RewriteEngine On
RewriteRule ^.well-known/webfinger https://chaos.social/.well-known/webfinger?resource=acct:manawyrm@chaos.social [R=302,L]

This will reply to all requests with a HTTP 302 (non-permanent!) redirect to our existing instance.
Remember to change both the domain name and the acct:-string.
The limitation of this method is that the query string/GET parameter isn’t getting parsed – every local part will get redirected to your account.

Advanced setup (using PHP)

Instead of redirecting directly to the existing ActivityPub instance, we can introduce another redirection and redirect the requesting instance to a custom script.

This can be written in any language of your choosing, of course. It only has to parse the resource GET parameter and reply either with a 302-redirect or a 404 error. For simple configurations, this can probably also be accomplished with very creative webserver configurations.

Every domain we want to use can be forwarded to our script:

RewriteEngine On
RewriteRule ^.well-known/webfinger https://mywebserver.de/activitypub-redirect/webfinger.php [R=302,L]

My webfinger.php looks like this:

<?php
switch (strtolower($_GET["resource"] ?? ''))
{
  case 'acct:user1@manawyrm.de':
  case 'acct:user1@kittenlabs.de':
    header("Location: https://existing-instance.tld/.well-known/webfinger?resource=acct:user1@existing-instance.tld");
    break;

  case 'acct:user2@kittenlabs.de':
    header("Location: https://existing-instance.tld/.well-known/webfinger?resource=acct:user2@existing-instance.tld");
    break;

  default:
    http_response_code(404);
    echo "404";
    break;
}

The switch-case statement can be expanded to support very advanced setups, of course.
It would be possible to use PHP to lookup the username in a database or even execute a full LDAP lookup and return the appropriate user account!

Hugo (static site generator)

When serving Hugo pages via Apache2, the hugo-apache-headers plugin can be used to generate the .htaccess file.

This plugin just needs a data/redirects.yaml file like this to generate a proper redirect:

redirects:
  - from: /.well-known/webfinger
    to: https://mywebserver.de/activitypub-redirect/webfinger.php

This will generate a 301 (permanent) redirect instead of a 302 (temporary) redirect, but it’ll still work.