2026年10月15日

2026年10月15日

WordPressでWebFingerに対応し、自分のドメインを分散型IDの起点にする

WebFinger は、acct:taro@example.com のような宛先から、その人のプロフィールや SNS アカウントの場所を調べるための仕組み(RFC 7033)です。WordPress で /.well-known/webfinger に応答させると、自分のドメインを名乗りの起点にして、Mastodon などから見つけてもらえるようになります。この記事では、WebFinger の仕組み、mu-plugin による最小限の実装、動かないときの確認方法、そして「分散型ID」とどこまで言えるのかを整理します。

ブログ全体を ActivityPub に対応させて Fediverse からフォローできるようにしたい場合は、ActivityPub プラグインが WebFinger も含めて処理します。この記事は、プラグインに頼らず WebFinger そのものを理解し、独自ドメインの名乗りを作りたい人向けです。

WebFinger の仕組み

WebFinger の問い合わせは、ドメイン直下の決まった URL に resource パラメーターを付けて行います。

GET https://example.com/.well-known/webfinger?resource=acct:taro@example.com

サーバーは JRD(JSON Resource Descriptor)という形式の JSON で答えます。

{
  "subject": "acct:taro@example.com",
  "aliases": [ "https://example.com/about/" ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://example.com/about/"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://mastodon.example/users/taro"
    }
  ]
}

links の各項目は、rel(その URL が何を表すか)と href(URL)の組です。Mastodon などは rel が self で type が application/activity+json の項目を探し、ActivityPub のアカウントの場所として扱います。

仕様上のポイントは次のとおりです。

  • 問い合わせは HTTPS で行う
  • 応答の Content-Type は application/jrd+json
  • ブラウザ上の JavaScript からも使えるよう、CORS(Access-Control-Allow-Origin)を許可することが推奨されている
  • resource が無い要求には 400、知らない宛先には 404 を返す

実装前に確認すること

ActivityPub プラグインや WebFinger プラグインを使っていないか: これらはすでに /.well-known/webfinger を処理しています。独自実装を重ねると、どちらの応答が返るか分からなくなります。使っている場合は独自実装をせず、プラグイン側の設定で対応してください。

WordPress がドメイン直下にあるか: WebFinger の URL はドメイン直下に固定されています。WordPress を https://example.com/blog/ のようなサブディレクトリに置いている場合、以下の WordPress 側の書き換えルールではドメイン直下の要求を受け取れません。その場合は、Web サーバーの設定でドメイン直下の /.well-known/webfinger を WordPress に転送するか、静的な JSON ファイルを置く方法を選びます。

公開するアカウントを決める: WordPress のユーザーを全員自動で応答させると、ユーザー名の一覧を外から推測されやすくなります。ログイン名は不正ログインの手がかりにもなるため、公開してよい名前だけを明示して応答させる設計にします。

mu-plugin で最小限の WebFinger を実装する

wp-content/mu-plugins/ に simple-webfinger.php を置きます。テーマを変えても動き続けるよう、テーマの functions.php ではなく mu-plugin にしています。作業の前にバックアップを取り、ステージング環境で試してください。

<?php
/**
 * Plugin Name: Simple WebFinger (example)
 * Description: 指定したアカウントだけ /.well-known/webfinger に応答する。
 */

// 公開してよいアカウントだけを列挙する(小文字で書く)
function wpdr_webfinger_accounts() {
    return array(
        'taro' => array(
            'profile' => 'https://example.com/about/',
            'actor'   => 'https://mastodon.example/users/taro',
        ),
    );
}

add_action( 'init', function () {
    add_rewrite_rule( '^\.well-known/webfinger/?$', 'index.php?wpdr_webfinger=1', 'top' );
} );

add_filter( 'query_vars', function ( $vars ) {
    $vars[] = 'wpdr_webfinger';
    return $vars;
} );

// canonical リダイレクト(優先度10)より先に処理する
add_action( 'template_redirect', function () {
    if ( ! get_query_var( 'wpdr_webfinger' ) ) {
        return;
    }

    header( 'Access-Control-Allow-Origin: *' );

    $resource = isset( $_GET['resource'] ) ? sanitize_text_field( wp_unslash( $_GET['resource'] ) ) : '';
    if ( '' === $resource ) {
        status_header( 400 );
        exit;
    }

    $host     = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
    $accounts = wpdr_webfinger_accounts();

    if ( ! preg_match( '/^acct:([a-z0-9._-]+)@([a-z0-9.-]+)$/i', $resource, $m ) ) {
        status_header( 404 );
        exit;
    }

    $name = strtolower( $m[1] );
    if ( strtolower( $m[2] ) !== $host || ! isset( $accounts[ $name ] ) ) {
        status_header( 404 );
        exit;
    }

    $account = $accounts[ $name ];
    $jrd     = array(
        'subject' => 'acct:' . $name . '@' . $host,
        'aliases' => array( esc_url_raw( $account['profile'] ) ),
        'links'   => array(
            array(
                'rel'  => 'http://webfinger.net/rel/profile-page',
                'type' => 'text/html',
                'href' => esc_url_raw( $account['profile'] ),
            ),
            array(
                'rel'  => 'self',
                'type' => 'application/activity+json',
                'href' => esc_url_raw( $account['actor'] ),
            ),
        ),
    );

    status_header( 200 );
    header( 'Content-Type: application/jrd+json; charset=utf-8' );
    echo wp_json_encode( $jrd, JSON_UNESCAPED_SLASHES );
    exit;
}, 1 );

コードの要点を補足します。

  • template_redirect を優先度 1 にしているのは、WordPress の canonical リダイレクト(同じフックの優先度 10)がパーマリンク設定によっては末尾にスラッシュを付けた URL へリダイレクトする場合があり、その前に応答するためです
  • resource は sanitize_text_field() で整えたうえで正規表現で形式を確かめ、ドメインが自サイトと一致し、かつ一覧にある名前だけに応答します
  • 仕様には rel パラメーターで返す項目を絞る機能がありますが、この例では省略してすべて返しています。問い合わせる側は絞り込まれない応答も扱えるようになっています

self に既存の Mastodon アカウントを指定すると、@taro@example.com で検索したときにそのアカウントが見つかるようになります。ただし Mastodon 上の表示名は、多くの場合アカウントが実際にあるサーバーの名前(@taro@mastodon.example)のままです。独自ドメインで「入口」を作る使い方と考えてください。

書き換えルールを追加したら、ルールを再生成します。管理画面の「設定」→「パーマリンク」を開いて変更を保存するか、WP-CLI で wp rewrite flush を実行します。

動作を確認する

# 正しい宛先:200 と JSON が返る
curl -i "https://example.com/.well-known/webfinger?resource=acct:taro@example.com"

# 一覧にない宛先:404 が返る
curl -i "https://example.com/.well-known/webfinger?resource=acct:admin@example.com"

期待どおりにならないときは、次の順で確認します。

  1. WordPress のトップページが返る: 書き換えルールが再生成されていません。パーマリンク設定を保存し直します
  2. Web サーバーの 404 や 403 が返る: Nginx などで、ドットで始まるパスをまとめて拒否する設定や、/.well-known/ を静的ファイル専用にする設定があると、PHP まで要求が届きません。/.well-known/webfinger だけ WordPress に渡すよう設定を見直します
  3. 301 リダイレクトが返る: canonical リダイレクトやキャッシュ、CDN のルールで末尾スラッシュ付きの URL に飛ばされています。フックの優先度と、キャッシュ・CDN の除外設定を確認します
  4. 古い応答が返る: ページキャッシュに残っています。/.well-known/ 配下をキャッシュ対象から外します

「分散型ID」とどこまで言えるか

WebFinger は、あくまで「宛先からプロフィールの場所を見つける」ための発見の仕組みです。本人であることを暗号技術で証明するものではありません。WebFinger が示すのは「このドメインを管理している人が、この情報を載せている」という事実で、信頼の根拠はドメインの管理にあります。ドメインの更新を忘れて第三者に取得されると、名乗りもそのまま奪われる点に注意してください。

ドメインを起点にした名乗りを補強する方法には、次のようなものがあります。

  • rel="me" による相互リンク: Mastodon は、プロフィール欄に書いたサイトの URL が rel="me" でプロフィールにリンクし返していると、そのリンクを確認済みとして表示します。WordPress 側では wp_head アクションで <link rel="me" href="..."> を出力するか、プロフィールへのリンクに rel="me" を付けます
  • Bluesky のドメインハンドル: DNS の TXT レコードか、ドメイン直下の /.well-known/atproto-did で、ドメインをハンドルとして使えます
  • W3C の DID(Decentralized Identifiers): 分散型 ID の規格としては W3C の DID があり、ドメイン上に DID 文書を置く方式もあります。ただし、利用できるサービスや方式は限られており、使い分けは目的に応じて検討が必要です(2026年10月現在)

WebFinger、rel="me"、ドメインハンドルを組み合わせると、「自分のドメインを持っていれば、どのサービスに移っても同じ名乗りを使い続けられる」状態に近づきます。これが、WordPress で WebFinger に対応する一番の意味です。

まとめ

  • WebFinger(RFC 7033)は、ドメイン直下の /.well-known/webfinger で宛先からプロフィールの場所を返す仕組み
  • ActivityPub プラグインなどを使っている場合は独自実装を重ねない
  • 独自実装では、公開するアカウントを明示し、application/jrd+json と CORS ヘッダーで応答する
  • サブディレクトリ設置や Web サーバーの /.well-known/ 制限、キャッシュで動かないことが多い
  • WebFinger は本人証明ではなく発見の仕組みで、rel="me" やドメインハンドルと組み合わせて名乗りを補強する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲