詳細については、メール プロバイダのモックのデモコードを確認し、メール検証 API と メール検証プロトコルの提案で発行者の手順を参照してください。
発行者は、オリジン トライアルに登録したり、トークンを提供したりする必要はありません。依拠当事者サイトがブラウザの動作をトリガーするためです。エンドポイントがこれらのリクエストに応答するように構成されていることを確認します。
発行者の検出を構成する
ドメインに属するメールアドレスが選択されたときに、ブラウザが検証エンドポイントを自動的に検出できるようにするには、DNS と .well-known HTTP エンドポイントを使用して構成を公開します。
DNS 委任レコードを構成する
メール ドメインに、検証権限を発行者 ID に委任する DNS TXT レコードを構成します。これらの識別子は、インフラストラクチャに応じて同じドメインを使用できます。
レコード形式: _email-verification.<email-domain>
ゾーンファイルの例:
_email-verification.example.com IN TXT "iss=accounts.issuer.example"
.well-known/email-verification エンドポイントをホストする
発行者のドメインの /.well-known/ パスに JSON メタデータ ファイルをホストします。このファイルには、発行機能と、インフラストラクチャがサポートする暗号署名アルゴリズムが記載されています。
エンドポイント: https://<issuer-domain>/.well-known/email-verification
レスポンスの例:
{
"issuance_endpoint": "https://accounts.issuer.example/email-verification/issuance",
"jwks_uri": "https://accounts.issuer.example/.well-known/vc-public-jwks",
"signing_alg_values_supported": ["EdDSA", "ES256"]
}
.well-known/web-identity エンドポイントをホストする
Federated Credentials(FedCM)API の一部として、追加の .well-known JSON リソースをすでに実装している可能性があります。このリソースは、アカウント エンドポイントとログイン URL へのリンクを提供します。
エンドポイント: https://<domain>/.well-known/web-identity
レスポンスの例:
{
"accounts_endpoint": "https://accounts.issuer.example/accounts",
"login_url": "https://accounts.issuer.example/login"
}
アカウント エンドポイントを使用する
FedCM API のアカウント エンドポイントは、現時点でログインしているアカウントのリストを提供します。次の例は、最小限のレスポンスを示しています。詳しくは、ID プロバイダの実装ガイドをご覧ください。
エンドポイント: .well-known/web-identity で指定されているとおり
レスポンスの例を次に示します。
{
"accounts": [
{
"id": "demo-example",
"name": "Demo User",
"email": "demo@example.com",
"given_name": "Demo"
}
]
}
ログイン ステータス API と統合する
ユーザーはプロバイダとのアクティブなセッションを保持している必要があり、ログイン ステータス API を使用してブラウザにそのことを通知する必要があります。
ユーザーがログインまたはログアウトに成功したら、一致する HTTP レスポンス ヘッダーを配信します。
Set-Login: logged-in
Set-Login: logged-out
または、ウェブ アプリケーション コンテキストで JavaScript を使用してステータスを更新します。
navigator.login.setStatus("logged-in");
navigator.login.setStatus("logged-out");
発行リクエストを処理する
issuance_endpoint は、email キーと Signature、Signature-Input、Signature-Key の HTTP メッセージ署名ヘッダーを含む application/json POST リクエストを受信します。
環境で構造化ヘッダーと HTTP メッセージ署名をサポートするライブラリを使用します。Node.js では、structured-headers と http-message-sig を使用できます。
完全なリクエスト形式:
POST /email-verification/issuance HTTP/1.1
Host: provider.example
Accept: application/json
Content-Digest: sha-256=:aBc123aBc123aBc123aBc123aBc123=:
Content-Type: application/json
Sec-Fetch-Dest: email-verification
Signature: sig=:+dEf567dEf567/dEf567dEf567dEf567/dEf567==:
Signature-Input: sig=("@method" "@authority" "@path" "content-digest" "signature-key");created=1786455840
Signature-Key: sig=hwk;crv="Ed25519";kty="OKP";x="gHi890_gHi890_gHi890"
{email: "demo@example.com"}
リクエストを解析して検証します。
- セッション認証: リクエストとともに送信されたファーストパーティ セッション Cookie を検証します。ユーザーは認証されている必要があります。
Sec-Fetch-Destヘッダー:email-verificationに設定します。- HTTP メッセージ署名:
Signature-Keyのエフェメラル公開鍵を使用してリクエスト署名を検証し、Content-Digestを検証します。 - ペイロード: JSON 本文には、検証用にリクエストされた
email文字列が含まれています。
発行レスポンス
セッション トークンとリクエスト トークンの検証に成功したら、プラットフォームに適したライブラリを使用して、JSON として返される署名付きの Selective Disclosure JWT(SD-JWT)を生成します。たとえば、Node の場合は @sd-jwt/core と jose を使用できます。
未加工のペイロードの形式は次のようになります。
{
"iss": "https://accounts.issuer.example",
"iat": 1780272000,
"exp": 1780272300,
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "pUbLiCKeY123pUbLiCKeY123pUbLiCKeY123",
"y": "pUbLiCKeY456pUbLiCKeY456pUbLiCKeY456"
}
},
"email": "demo@example.com",
"email_verified": true
}
トークンを作成、署名して返します。
import { importJWK, CompactSign } from "jose";
import { SDJwtInstance } from "@sd-jwt/core";
import crypto from "node:crypto";
// Issuer private key from secure storage
const privateKey = await importJWK(PRIVATE_KEY_JWK, "EdDSA");
const origin = url.origin;
const currentTime = Math.floor(Date.now() / 1000);
const evtPayload = {
iss: origin,
iat: currentTime,
exp: currentTime + 300, // 5 minutes
cnf: {
jwk: browserJwk,
},
email: payload.email, // exactly as received in payload
email_verified: true,
};
const sdJwt = new SDJwtInstance({
signer: async (data) => {
const [headerB64, payloadB64] = data.split(".");
const header = JSON.parse(Buffer.from(headerB64, "base64url").toString());
const payload = Buffer.from(payloadB64, "base64url");
const signed = await new CompactSign(payload).setProtectedHeader(header).sign(privateKey);
return signed.split(".").pop()!;
},
signAlg: "EdDSA",
hasher: async (data, alg) => {
const nodeAlg = alg.replace("-", "");
return new Uint8Array(crypto.createHash(nodeAlg).update(data).digest());
},
hashAlg: "sha-256",
saltGenerator: async () => crypto.randomBytes(16).toString("base64url"),
});
const issuanceToken = await sdJwt.issue(evtPayload, undefined, {
header: {
alg: "EdDSA",
kid: PRIVATE_KEY_JWK.kid,
typ: "evt+jwt",
},
});
return sendResponse({ issuance_token: issuanceToken, }, 200);
レスポンス本文は次のようになります。
{
"issuance_token": "tOkEn123tOkEn123tOkEn123...~"
}
メール確認トークンを作成して署名すると、ブラウザはこのトークンを検証サイトに渡して検証します。
トラブルシューティング
ブラウザがエンドポイントに接続しない場合や、発行されたトークンが拒否された場合は、次の一般的な問題を確認してください。
ブラウザが accounts_endpoint または issuance_endpoint を呼び出さない
- ログイン ステータスが設定されていない: Chrome は、ユーザーがログインしていることがわかっている場合にのみ、エンドポイントをクエリします。ログインフローで
Set-Login: logged-inHTTP ヘッダーが設定されているか、navigator.login.setStatus("logged-in")が呼び出されていることを確認します。 - セッション Cookie がブロックされた(
SameSite=None): ブラウザが別のサイトの証明書利用者からaccounts_endpointとissuance_endpointを取得します。これらはクロスサイト リクエストであるため、セッション Cookie にSameSite=None; Secureを含める必要があります。SameSite=LaxCookie は、同じサイトの検証ツールでテストする場合は機能しますが、クロスサイト リクエストでは省略されます。 - 検出またはアカウントの不一致:
_email-verification.<email-domain>が単一の TXT レコード(iss=<issuer-domain>、https://なし)を返し、両方の.well-knownエンドポイントがContent-Type: application/jsonを返し、accounts_endpointレスポンスに入力されたアドレスとemailが一致するアカウントが含まれていることを確認します。
発行リクエストの検証に失敗する
Sec-Fetch-Destヘッダーのスペル: Chrome 154 以降ではSec-Fetch-Dest: email-verification(ハイフンあり)が送信されますが、Chrome 153 ではemailverification(ハイフンなし)が送信されていました。ロールアウト中に両方の値を受け入れます。- プロキシの背後にある HTTP メッセージ署名(
@authority)の不一致: RFC 9421 署名を確認するときに、@authorityコンポーネントが公開ホストを反映します。サーバーがリバース プロキシまたはロードバランサの背後にある場合は、内部ホスト名ではなくX-Forwarded-Host(または公開オリジン)を使用して確認用 URL を再構築し、JSON 解析の前に未加工のリクエスト本文のバイトに対してContent-Digestを計算します。
ブラウザが返された issuance_token を拒否する
- 変更または正規化された
emailクレーム: Chrome 156 以降では、ブラウザは EVT のemailクレームがリクエストされたemailとバイト単位で一致することを確認します。バックエンドがアドレスを正規アカウント形式に正規化する場合(first.LAST@example.comがリクエストされたときにFirst.Last@example.comを返すなど)、Chrome はトークンを削除します。リクエストをユーザーのアカウントと照合しますが、リクエストの本文で受け取ったemail文字列をそのまま返します。 - 末尾のチルダ(
~)がない: 開示が 1 つもない場合でも、issuance_tokenは末尾にチルダ(<Issuer-signed-JWT>~)が付いた有効な SD-JWT でなければなりません。そうすることで、ブラウザが<KB-JWT>を追加できるようになります。 issまたはcnf.jwkの不一致: EVT のissクレームが.well-known/email-verificationメタデータと一致する正確な HTTPS オリジン(https://<issuer-domain>、末尾のスラッシュなし)であり、cnf.jwkがSignature-Keyヘッダーからブラウザの一時公開鍵を埋め込んでいることを確認します。