1. はじめに
従来、アカウント作成、サインイン、またはアカウント復旧時のメールアドレスの検証は、 手動による帯域外の仕組みに依存してきました。一般的なフローでは、Web サイトがワンタイムパスコード(OTP)または 「マジックリンク」をユーザーのメールアドレスに送信します。その後、ユーザーはメールの受信トレイに移動し、 コードを取得するかリンクをクリックしてから、Web サイトに戻って所有権を証明する必要があります。
このプロセスは大きな摩擦をもたらし、ユーザー体験とコンバージョン率に影響を与えます。さらに、 悪意のあるサイトがユーザーを欺いて OTP を提供させるフィッシング攻撃に対して脆弱です。
メール検証プロトコル(EVP)は、メールの所有権を暗号学的に検証するための、ブラウザーを介した仕組みを導入します。 ユーザーとメールプロバイダー(発行者)の間のアクティブなセッションを活用することで、 ブラウザーは暗号学的に署名されたメール検証トークン(EVT)を要求し、それを Web サイト(検証者)に提示できます。
この仕様は、検証者が EVT を要求するために使用する HTML の拡張と、トークンを取得するために発行者と連携する クライアント側のブラウザーの動作を定義します。
1.1. プロトコルの概要
このプロトコルには、3 つの主要な当事者が関与します:
-
検証者: メール検証を要求する Web サイト。
-
ユーザーエージェント(UA): 要求を仲介するブラウザー。
-
発行者: メールアドレスに対して権威を持つメールプロバイダー。
一般的なフローは、次の手順で構成されます:
-
ログイン: ユーザーがメールプロバイダーにログインします。プロバイダーはブラウザーの ログイン状態を ログイン済み に更新します( ログイン状態 API を使用します)。
-
要求: 検証者の Web サイトには、
autocomplete="email-verification-token"とnonce属性を持つ非表示の入力フィールドが含まれます。 -
発見と検証: ユーザーがメールアドレスを選択すると(たとえば、 自動入力を介して)、UA は DNS を介して権威のある発行者を発見します。UA は、ユーザーがその発行者との アクティブなセッションを持っているかどうかを確認します。
-
発行: UA は発行者に EVT を要求します。
-
バインドと提示: UA は検証者のオリジンと nonce を EVT にバインドし、 鍵バインド JWT(KB-JWT)を作成して、フォームの 送信前に検証者の入力フィールドへ入力します。
-
検証: 検証者は EVT と KB-JWT を検証して、検証を完了します。
1.2. 例
この節は非規範的です。
登録時にユーザーのメールアドレスを検証しようとする検証者の Web サイト
https://rp.example を考えます。
1.2.1. 検証者の HTML フォーム
検証者は、標準のメール入力と、メール検証トークン(EVT)用の非表示入力を、
autocomplete="email-verification-token" および一意の nonce とともに含めます:
< form action = "/signup" method = "post" > < label for = "email" > メールアドレス:</ label > < input type = "email" id = "email" name = "email" autocomplete = "email" > <!-- EVP の非表示入力 --> < input type = "hidden" name = "evt" autocomplete = "email-verification-token" nonce = "xyz123456789" > < button type = "submit" > 登録</ button > </ form >
1.2.2. ブラウザーとの対話
-
ユーザーが
email入力にフォーカスすると、ブラウザーは自動入力用のメールアドレスを 提案します(たとえば、user@email.example)。 -
ユーザーは
user@email.exampleを選択します。 -
ブラウザーは発行者の発見(
email.exampleがissuer.exampleに委任されていることの検出)を実行し、発行者とのユーザーのセッションを検証して、次のプロンプトを表示します: メールを検証しますか?user@email.exampleの検証済みトークンをrp.exampleと共有しますか? [許可] [拒否] -
ユーザーが「許可」を選択すると、ブラウザーは
issuer.exampleから EVT を取得し、それをrp.exampleおよび noncexyz123456789にバインドして、バインドされたトークンを保存します。
1.2.3. フォームの送信
ユーザーがフォームを送信すると、ブラウザーはバインドされたトークンを非表示の入力 フィールドに自動的に挿入します。検証者は次の POST ペイロードを受信します:
POST /signup HTTP / 1.1 Host : rp.example Content-Type : application/x-www-form-urlencoded = user%40email.example & evt = eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjQtMDgtMTkiLCJ0eXAiOiJldnQrand0In0...
検証者は次に evt トークンを解析して検証し(署名、オリジン
rp.example、nonce xyz123456789、およびメール
user@email.example の一致を検証します)、検証メールを送信することなく登録を完了します。
2. HTML の拡張
この仕様は、新しい自動入力フィールド名を導入し、
nonce 属性の使用を拡張することにより、HTML 標準 [HTML] を拡張します。
2.1. email-verification-token 自動入力値
email-verification-token キーワードが、[HTML] で定義される自動入力 フィールド名(具体的には、詳細トークン)の一覧に追加されます。
<input> 要素の autocomplete 属性が
email-verification-token に設定されている場合、同じフォーム内でユーザーが
メールアドレスを選択して検証していることを条件として、ユーザーエージェントはフォーム送信時にこのフィールドへ
暗号学的にバインドされたメール検証トークン(EVT)を入力するよう試みるべきであることを示します。
通常、このキーワードは <input type="hidden"> 要素で使用されます。
2.2. input 要素の nonce 属性
この仕様は、もともと [HTML] の
<script> および
<style> 要素に対して定義され(コンテンツセキュリティポリシー [CSP] で使用される)、
nonce 属性を
<input> 要素に拡張します。
autocomplete="email-verification-token" を持つ <input> 要素に指定された場合、
nonce 属性には、サーバー側で生成された暗号学的に強いランダム値(
暗号 nonce)が含まれます。
この nonce は、生成される EVT を特定のフォーム提示にバインドし、リプレイ攻撃を防ぐために使用されます。
nonce 属性の値は、ページをレンダリングするたびに一意でなければなりません。
3. ブラウザーの処理モデル
3.1. 自動入力の選択の処理
ユーザーが、<form> form 内の
<input> 要素 emailInput に対するユーザーエージェントの自動入力候補から、メールアドレス
email を選択した場合:
-
form 内で、値が
email-verification-tokenであるautocomplete属性を持つ最初の<input>要素を evtInput とします。 -
そのような evtInput が存在しない場合、これらの手順を終了します。
-
evtInput の
nonce属性の値を nonce とします。 -
nonce が空の場合、これらの手順を終了します。
-
form の EVP 状態を次のように設定します:
-
email: email -
inputElement: emailInput -
token: null
-
-
バックグラウンドで、次の手順を実行します:
-
email に対して [EVP-Protocol] で定義される 発行者の 発見 の手順を実行した結果を issuer とします。
-
issuer が null の場合、これらの手順を終了します。
-
email と issuer に対して アカウントの検証を実行した結果を accountMatch とします。
-
accountMatch が false の場合、これらの手順を終了します。
-
メールアドレス email を issuer で検証する許可を求めるユーザープロンプトを表示します。
-
ユーザーが許可を拒否した場合、これらの手順を終了します。
-
issuer から email に対する EVT の発行を実行した結果を evtResult とします。
-
evtResult が null の場合、これらの手順を終了します。
-
evt を evtResult[0] とします。
-
keyPair を evtResult[1] とします。
-
keyPair の秘密鍵コンポーネントを使用し、nonce と文書のオリジンを指定して、 evt に対して [EVP-Protocol] で定義される 鍵バインドの 作成 の手順を実行した結果を kbEvt とします。
-
form の EVP 状態を state とします。
-
state が null ではなく、state の
emailが email と ASCII 大文字・小文字を区別せずに等しい場合:-
state の
tokenを kbEvt に設定します。
-
-
3.2. フォーム送信との統合
<form> form が送信された場合:
-
form の EVP 状態を state とします。
-
state が null、または state の
tokenが null の場合、標準の フォーム送信手順を続行します。 -
state の
inputElementの値を currentEmail とします。 -
currentEmail が state の
emailと ASCII 大文字・小文字を区別せずに等しい場合:-
form 内で、値が
email-verification-tokenであるautocomplete属性を 持つ最初の<input>要素を evtInput とします。 -
evtInput が存在する場合:
-
evtInput の値を state の
tokenに設定します。
-
-
-
[HTML] で定義される標準のフォーム送信手順を続行します。
3.3. アカウントの検証
メールアドレス email と発行者ドメイン issuer が与えられた場合、ユーザーエージェントは アカウントを検証するために次の手順を実行しなければなりません:
-
ログイン状態 API [login-status] を使用して、issuer のログイン状態を確認します。
-
状態が ログアウト済み の場合、 false を返します。
-
[fedcm] で定義される、 issuer の well-known ファイルを取得した結果を wellKnown とします。
-
wellKnown が null の場合、false を返します。
-
wellKnown の
accounts_endpointメンバーの値を accountsEndpoint とします。 -
accountsEndpoint が存在しないか、有効な URL ではない場合、false を返します。
-
issuer と accountsEndpoint に対して、 [fedcm] で定義される アカウントを取得するアルゴリズムを実行した結果を accounts とします。
-
accounts が null または空の場合、false を返します。
-
accounts 内の各 account について:
-
account の
emailメンバーの値を accountEmail とします。 -
accountEmail が email と ASCII 大文字・小文字を区別せずに等しい場合、true を返します。
-
-
false を返します。
3.4. EVT の 発行
メールアドレス email と発行者ドメイン issuer が与えられた場合、ユーザーエージェントは EVT を取得するために次の手順を実行しなければなりません:
-
一時的な非対称鍵ペア keyPair を生成します(発行者のメタデータで定義される、 発行者が対応するアルゴリズムを使用し、指定されていない場合は Ed25519 を既定値とします)。
-
JSON Web Token [JWT] requestToken を構築します:
-
ヘッダーには次のものを含めなければなりません:
-
alg: 署名アルゴリズム(keyPair のアルゴリズムと一致するもの)。 -
jwk: keyPair の公開鍵コンポーネント。
-
-
ペイロードには次のものを含めなければなりません:
-
aud: issuer の識別子(ドメイン)。 -
iat: 現在時刻。 -
email: email アドレス。
-
-
keyPair の秘密鍵コンポーネントを使用して requestToken に署名します。
-
-
発行者のトークン発行エンドポイント(発行者のメタデータから取得される)を issuanceUrl とします。
-
issuanceUrl に対する HTTP POST 要求 request を構築します:
-
Content-Typeヘッダーをapplication/x-www-form-urlencodedに設定します。 -
Sec-Fetch-Destヘッダーをemail-verificationに設定します。 -
issuer のファーストパーティ Cookie を含めます。
-
要求の本体を次の URL エンコードされた表現に設定します:
request_token= requestToken(文字列として直列化)。
-
-
request を送信し、応答 callResponse を待ちます。
-
callResponse のステータスコードが 200 OK ではないか、その
Content-Typeがapplication/jsonではない場合、null を返します。 -
callResponse の本体を JSON として解析し、その結果を json とします。
-
json に
issuance_tokenが含まれていない場合、null を返します。 -
json の
issuance_tokenを evt とします。 -
evt を検証します:
-
evt が、発行者によって署名された有効な SD-JWT [SD-JWT] であることを検証します。
-
evt 内の
cnfクレームに、keyPair の 公開鍵と一致する公開鍵が含まれていることを検証します。 -
evt 内の
emailクレームが email と一致することを検証します。
-
-
検証に失敗した場合、null を返します。
-
(evt, keyPair)を含むタプルを返します。
4. 検証者の処理モデル
検証者が、メールアドレス email とバインドされたトークン
boundToken(autocomplete="email-verification-token"
を持つ入力フィールドから取得)を含む送信済みフォームを受信した場合:
-
検証者のオリジンと期待される nonce に対して、boundToken に [EVP-Protocol] で定義される トークンの 検証 の手順を実行します。
-
検証に失敗した場合、検証を失敗させます。
-
検証済みトークンから抽出されたメールアドレスを verifiedEmail とします。
-
verifiedEmail が email と ASCII 大文字・小文字を区別せずに等しくない場合、検証を失敗させます。
-
それ以外の場合、検証は成功します。
5. セキュリティとプライバシーに関する考慮事項
5.1. プライバシー
5.1.1. 発行者のブラインド化
EVP の主要なプライバシー目標は、ユーザーがどの検証者と対話しているかを発行者が知ることを防ぐことです。ユーザーエージェントは、Well-Known の取得および Accounts の取得によって検証者の
オリジンが漏えいしないことを確保しなければなりません。
発行要求は資格情報を伴いますが、要求ヘッダーに検証者のオリジンを含めてはなりません
(たとえば、Referer または Origin は省略しなければなりません)。
5.1.2. 追跡のリスク
発行要求では Cookie が使用されるため、発行者はユーザーがアクティブであることを知ります。ただし、これは ユーザーが検証のために自らメールを選択した場合にのみ知られるものであり、ユーザーが開始した操作です。5.2. セキュリティ
5.2.1. リプレイ攻撃
提示トークン(EVT+KB)は、鍵バインド JWT を介して特定のnonce および audience(検証者の
オリジン)にバインドされます。
検証者は次のことを検証しなければなりません:
-
audienceが自身のオリジンと一致すること。 -
nonceが、フォーム提示用に生成したものと一致すること。 -
expクレームの有効期限が切れていないこと。
これにより、攻撃者が EVT+KB を傍受し、別のサイトまたは異なる コンテキストで再利用することを防ぎます。