2026年10月11日

2026年10月11日

WordPressの著者プロフィールページをW3CのDID(分散型識別子)で強化する方法

WordPress の著者プロフィールページに W3C の DID(Decentralized Identifiers、分散型識別子)を結び付けるなら、現実的なのは「did:web 方式の DID ドキュメントを自サイトから JSON で公開し、著者ページにその DID を表示・構造化データで記述する」やり方です。この記事では、ユーザープロフィールへの DID 入力欄の追加、著者ごとの DID ドキュメントの配信、著者アーカイブへの出力、動作確認までを、mu-plugin 1枚で実装する手順を説明します。

なお、DID を設定しても検索順位が上がるわけではありません。Google が DID を評価に使うという公式な説明は(2026年10月現在)見当たらず、目的は「この著者とこのドメインの結び付きを、機械が読める形で第三者に検証してもらえる状態を作ること」だと考えてください。

DID と did:web の基本

DID は W3C が 2022年に勧告(Recommendation)として公開した「Decentralized Identifiers (DIDs) v1.0」で定義された識別子です。did:方式名:方式ごとの識別子 という形をしており、DID を「解決(resolve)」すると、その主体の公開鍵や関連 URL を記した DID ドキュメント(JSON)が得られます。

方式(DID method)は多数ありますが、WordPress サイトで扱いやすいのは did:web です。did:web は DID を HTTPS の URL に機械的に変換する方式で、次のように対応します。

  • did:web:example.com → https://example.com/.well-known/did.json
  • did:web:example.com:did:alice → https://example.com/did/alice/did.json

コロンで区切られた後半がパスになり、末尾に did.json が付きます。つまり「そのドメインを管理している人だけが DID ドキュメントを置ける」ことが信頼の根拠です。逆に言えば、ドメインを手放したり HTTPS の設定が崩れたりすると DID も機能しなくなります。did:web の仕様は W3C の Credentials Community Group による文書で、DID Core のような正式勧告ではない点も理解しておくと、説明を求められたときに誤解を招きません。

DID Core では、DID ドキュメントで必須なのは id だけです。alsoKnownAs(同一主体の別の識別子や URL)や verificationMethod(公開鍵)は任意項目なので、まずは「DID と著者ページの URL を相互に指し示す」最小構成から始め、署名が必要になった段階で公開鍵を追加するのが無理のない進め方です。

作業前の準備

  • 本番の前にステージング環境で試します。リライトルールを追加するため、パーマリンク設定やキャッシュとの相性を確認しておきたいからです。
  • データベースとファイルのバックアップを取ります。
  • サイトが HTTPS で配信されていることを確認します。did:web は HTTPS が前提です。
  • コードはテーマに依存しないよう wp-content/mu-plugins/author-did.php に置きます。テーマを変えても DID の URL が変わらないようにするためです。

秘密鍵は WordPress に保存しません。署名用の鍵を使う場合も、WordPress に置くのは公開鍵だけにし、秘密鍵は手元の安全な環境で管理します。

ステップ1:プロフィール画面に DID 入力欄を追加する

まず、各ユーザーが自分の DID を登録できる欄を「プロフィール」画面に追加します。空欄のユーザーには DID ドキュメントを公開しない「オプトイン」方式にしておくと、意図しないユーザー(管理者アカウントなど)の情報が外に出ません。

<?php
/**
 * Plugin Name: Author DID
 * Description: 著者プロフィールに DID を登録し、did:web の DID ドキュメントを公開する。
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

const WPDR_DID_META = 'wpdr_author_did';

// このサイトで公開する did:web の DID を、著者のスラッグから組み立てる。
function wpdr_did_web_for( WP_User $user ) {
	$host     = wp_parse_url( home_url(), PHP_URL_HOST );
	$path     = trim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' );
	$segments = '' === $path ? array() : explode( '/', $path );
	$segments[] = 'did';
	$segments[] = rawurlencode( $user->user_nicename );
	return 'did:web:' . $host . ':' . implode( ':', $segments );
}

add_action( 'show_user_profile', 'wpdr_did_field' );
add_action( 'edit_user_profile', 'wpdr_did_field' );
function wpdr_did_field( $user ) {
	$did = get_user_meta( $user->ID, WPDR_DID_META, true );
	?>
	<h2>分散型識別子(DID)</h2>
	<table class="form-table" role="presentation">
		<tr>
			<th><label for="wpdr_author_did">DID</label></th>
			<td>
				<input type="text" class="regular-text" id="wpdr_author_did" name="wpdr_author_did"
					value="<?php echo esc_attr( $did ); ?>"
					placeholder="<?php echo esc_attr( wpdr_did_web_for( $user ) ); ?>">
				<p class="description">空欄の場合、DID ドキュメントは公開されません。</p>
			</td>
		</tr>
	</table>
	<?php
}

add_action( 'personal_options_update', 'wpdr_did_save' );
add_action( 'edit_user_profile_update', 'wpdr_did_save' );
function wpdr_did_save( $user_id ) {
	if ( ! current_user_can( 'edit_user', $user_id ) ) {
		return;
	}
	check_admin_referer( 'update-user_' . $user_id );

	$did = isset( $_POST['wpdr_author_did'] )
		? sanitize_text_field( wp_unslash( $_POST['wpdr_author_did'] ) )
		: '';

	if ( '' === $did ) {
		delete_user_meta( $user_id, WPDR_DID_META );
		return;
	}
	// did:方式名:識別子 の形だけを受け付ける。
	if ( preg_match( '/^did:[a-z0-9]+:[A-Za-z0-9._:%-]+$/', $did ) ) {
		update_user_meta( $user_id, WPDR_DID_META, $did );
	}
}

プロフィール画面のフォームには WordPress 標準の nonce(update-user_{ユーザーID})が含まれているため、check_admin_referer() でそれを検証し、さらに current_user_can( 'edit_user', $user_id ) で編集権限を確かめています。入力値は sanitize_text_field() で整え、正規表現で DID の形をしているものだけを保存します。入力欄のプレースホルダーには、このサイトで公開される did:web の値を表示しているので、自サイトの DID を使う場合はそれをそのまま貼り付ければ済みます。

ステップ2:著者ごとの DID ドキュメントを配信する

次に、https://example.com/did/{著者スラッグ}/did.json にアクセスされたら DID ドキュメントを JSON で返すようにします。同じ mu-plugin に追記します。

add_action( 'init', function () {
	add_rewrite_rule( '^did/([^/]+)/did\.json$', 'index.php?wpdr_did_user=$matches[1]', 'top' );
} );

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

add_action( 'template_redirect', function () {
	$slug = get_query_var( 'wpdr_did_user' );
	if ( '' === $slug ) {
		return;
	}

	$user = get_user_by( 'slug', sanitize_title( $slug ) );
	$did  = $user ? get_user_meta( $user->ID, WPDR_DID_META, true ) : '';

	// 登録された DID が、この URL に対応する did:web と一致する場合だけ公開する。
	if ( ! $user || $did !== wpdr_did_web_for( $user ) ) {
		wp_send_json( array( 'error' => 'not found' ), 404 );
	}

	$document = array(
		'@context'    => array( 'https://www.w3.org/ns/did/v1' ),
		'id'          => $did,
		'alsoKnownAs' => array( get_author_posts_url( $user->ID ) ),
	);

	header( 'Access-Control-Allow-Origin: *' );
	wp_send_json( $document, 200, JSON_UNESCAPED_SLASHES );
} );

ポイントは、登録された DID が「この URL から導かれる did:web」と一致する場合だけ公開している点です。別の方式の DID(たとえば外部サービスで発行した DID)を登録したユーザーについては、自サイトで DID ドキュメントを偽って配信しないようにしています。DID リゾルバーがブラウザ経由で取得する場合に備えて Access-Control-Allow-Origin も付けています。

追加したリライトルールは、「設定」→「パーマリンク」を開いて保存するまで有効になりません。mu-plugin には有効化フックがないため、この操作を忘れると 404 になります。また、did/ で始まる固定ページや投稿スラッグがあると衝突するので、事前に確認してください。

公開鍵を載せたい場合は、手元で生成した鍵の公開部分(JWK など)を別のユーザーメタに保存し、verificationMethod と assertionMethod を配列に加えます。どの鍵形式を使うかは、DID を検証する相手側のツールが対応している形式に合わせるのが確実性の高い選び方です。

ステップ3:著者ページに DID を表示し、構造化データに含める

著者アーカイブ(/author/{スラッグ}/)に、人にも機械にも読める形で DID を出します。構造化データは schema.org の Person を使い、identifier に DID を入れます。

add_action( 'wp_head', function () {
	if ( ! is_author() ) {
		return;
	}
	$user = get_queried_object();
	if ( ! $user instanceof WP_User ) {
		return;
	}
	$did = get_user_meta( $user->ID, WPDR_DID_META, true );
	if ( ! $did ) {
		return;
	}

	$data = array(
		'@context'   => 'https://schema.org',
		'@type'      => 'Person',
		'name'       => $user->display_name,
		'url'        => get_author_posts_url( $user->ID ),
		'identifier' => $did,
	);
	if ( $user->user_url ) {
		$data['sameAs'] = array( esc_url_raw( $user->user_url ) );
	}

	echo '<script type="application/ld+json">'
		. wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG )
		. "</script>\n";
}, 20 );

JSON_HEX_TAG を付けているのは、表示名などに </script> のような文字列が含まれていてもスクリプトタグが途中で閉じないようにするためです。

画面上の表示は、子テーマの author.php などに echo esc_html( get_the_author_meta( 'wpdr_author_did', $user_id ) ); のような形で加えます。出力時は esc_html() でエスケープします。

Yoast SEO や Rank Math などの SEO プラグインは、著者アーカイブに独自の Person 構造化データを出力することがあります。同じ人物の Person が二重に出ると解釈が分かれるため、プラグイン側のスキーマ用フィルターで identifier を追記する方法を選ぶか、どちらか一方に寄せてください。フィルター名や仕様はプラグインのバージョンで変わることがあるので、各プラグインの開発者向けドキュメントで確認します。

ステップ4:動作を確認する

  1. ターミナルで curl -i https://example.com/did/alice/did.json を実行し、ステータス 200、Content-Type が JSON、本文の id が登録した DID と一致することを確認します。
  2. DID を空欄にしたユーザーや存在しないスラッグで 404 が返ることを確かめます。
  3. 公開されている汎用の DID リゾルバー(Universal Resolver など)に did:web の DID を入力し、同じドキュメントが取得できるかを見ます。サービスの提供状況は変わることがあるため、つながらない場合は curl の結果を優先してください。
  4. 著者ページのソースに JSON-LD が出ていることを確認し、Schema Markup Validator で構文エラーがないかを確認します。

キャッシュプラグインや CDN を使っている場合、did.json が古い内容のまま返ることがあります。DID ドキュメントを更新したときはキャッシュを削除し、必要ならこのパスをキャッシュ対象から外します。

運用上の注意

  • 著者スラッグ(user_nicename)が URL に含まれます。ログイン名と同じスラッグになっているユーザーは、表示用のスラッグに変えておくと安全です。
  • ドメインを移転すると did:web の DID は別物になります。移転の予定があるサイトでは、移転後に旧 DID をどう扱うかを先に決めておきます。
  • DID は個人を長期的に識別する情報です。著者本人の同意を得たうえで登録し、プライバシーポリシーに「著者が任意で公開する識別情報」として記載しておくと、問い合わせがあったときに説明しやすくなります。

まとめ

  • WordPress で DID を扱うなら did:web が現実的で、did:web:ドメイン:did:スラッグ を /did/スラッグ/did.json に対応させます。
  • プロフィールへの入力欄は nonce と権限チェック、サニタイズを通し、空欄のユーザーは公開しないオプトイン方式にします。
  • DID ドキュメントは id と alsoKnownAs の最小構成から始め、公開鍵は必要になってから追加します。秘密鍵は WordPress に置きません。
  • 著者ページには schema.org の Person と identifier で DID を記述し、SEO プラグインの出力との重複に注意します。
  • DID は順位対策ではなく、著者とドメインの結び付きを検証可能にする仕組みとして位置付けます。

お気軽にご相談ください

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