1. 简介
以往,在创建账户、登录或恢复账户期间验证电子邮件地址,一直依赖 手动的带外机制。典型流程是网站向用户的电子邮件地址发送一次性密码(OTP)或 “魔法链接”。随后,用户必须前往电子邮件收件箱,获取 密码或点击链接,然后返回网站以证明其所有权。
此过程会带来显著阻力,影响用户体验和转化率。此外,它 容易受到网络钓鱼攻击,恶意网站可能诱骗用户提供 OTP。
电子邮件验证协议(EVP)引入了一种由浏览器中介的机制,以通过密码学方式验证电子邮件所有权。 通过利用用户与其电子邮件提供商(颁发者)之间的活跃会话, 浏览器可以请求经过密码学签名的电子邮件验证令牌(EVT),并将其呈现给 网站(验证者)。
本规范定义了验证者用于请求 EVT 的 HTML 扩展,以及用于与颁发者协调 获取令牌的客户端浏览器行为。
1.1. 协议概述
该协议涉及三个主要参与方:
-
验证者:请求电子邮件验证的网站。
-
用户代理(UA):对请求进行中介的浏览器。
-
颁发者:对该电子邮件地址具有权威性的电子邮件提供商。
典型流程包括以下步骤:
-
请求:验证者的网站包含一个具有
autocomplete="email-verification-token"和nonce属性的隐藏输入字段。 -
发现与验证:当用户选择电子邮件地址时(例如通过 自动填充),UA 通过 DNS 发现权威颁发者。UA 检查用户是否与该颁发者具有 活跃会话。
-
颁发:UA 从颁发者请求 EVT。
-
绑定与呈现:UA 将验证者的源和 nonce 绑定到 EVT, 创建密钥绑定 JWT(KB-JWT),并在表单 提交前将其填入验证者的输入字段。
-
验证:验证者验证 EVT 和 KB-JWT,以完成验证。
1.2. 示例
本节是非规范性的。
假设验证者网站 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),验证用户与颁发者的会话,并显示提示: 验证您的电子邮件? 您是否要与rp.example共享user@email.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> 元素。
当 nonce 属性存在于具有 autocomplete="email-verification-token" 的
<input> 元素上时,它包含一个由服务器端生成的、密码学强度足够的随机值(即
密码学 nonce)。
此 nonce 用于将生成的 EVT 绑定到特定的表单呈现,从而防止重放攻击。
nonce 属性的值对于每次页面渲染都必须是唯一的。
3. 浏览器处理模型
3.1. 处理自动填充选择
当用户从用户代理针对 <form> form 内的
<input> 元素 emailInput 所提供的自动填充建议中选择电子邮件地址
email 时:
-
令 evtInput 为 form 中第一个具有
autocomplete属性且该属性值为email-verification-token的<input>元素。 -
如果不存在这样的 evtInput,则终止这些步骤。
-
令 nonce 为 evtInput 的
nonce属性值。 -
如果 nonce 为空,则终止这些步骤。
-
将 form 的 EVP 状态设置为:
-
email:email -
inputElement:emailInput -
token:null
-
-
在后台执行以下步骤:
-
令 issuer 为对 email 执行 [EVP-Protocol] 中定义的颁发者 发现步骤所得的结果。
-
如果 issuer 为 null,则终止这些步骤。
-
令 accountMatch 为使用 issuer 对 email 执行账户验证所得的结果。
-
如果 accountMatch 为 false,则终止这些步骤。
-
显示用户提示,请求允许使用 issuer 验证电子邮件地址 email。
-
如果用户拒绝授权,则终止这些步骤。
-
令 evtResult 为从 issuer 对 email 执行 EVT 颁发所得的结果。
-
如果 evtResult 为 null,则终止这些步骤。
-
令 evt 为 evtResult[0]。
-
令 keyPair 为 evtResult[1]。
-
令 kbEvt 为使用 keyPair 的私钥组件,并结合 nonce 和文档的源,对 evt 执行 [EVP-Protocol] 中定义的密钥绑定 创建步骤所得的结果。
-
令 state 为 form 的 EVP 状态。
-
如果 state 不为 null,且 state 的
email与 email 不区分大小写地相等:-
将 state 的
token设置为 kbEvt。
-
-
3.2. 表单提交集成
当提交 <form> form 时:
-
令 state 为 form 的 EVP 状态。
-
如果 state 为 null,或 state 的
token为 null,则继续执行标准 表单提交步骤。 -
令 currentEmail 为 state 的
inputElement的值。 -
如果 currentEmail 与 state 的
email不区分大小写地相等:-
令 evtInput 为 form 中第一个 具有
autocomplete属性且该属性值为email-verification-token的<input>元素。 -
如果 evtInput 存在:
-
将 evtInput 的值设置为 state 的
token。
-
-
-
继续执行 [HTML] 中定义的标准表单提交步骤。
3.3. 账户验证
给定电子邮件地址 email 和颁发者域 issuer,用户代理必须执行 以下步骤来验证账户:
-
使用登录状态 API [login-status] 检查 issuer 的登录状态。
-
如果状态为已退出登录, 则返回 false。
-
令 wellKnown 为按照 [fedcm] 中的定义,为 issuer 获取well-known 文件所得的结果。
-
如果 wellKnown 为 null,则返回 false。
-
令 accountsEndpoint 为 wellKnown 的
accounts_endpoint成员的值。 -
如果 accountsEndpoint 不存在或不是有效的 URL,则返回 false。
-
令 accounts 为针对 issuer 和 accountsEndpoint 执行 [fedcm] 中定义的获取账户算法所得的结果。
-
如果 accounts 为 null 或为空,则返回 false。
-
对于 accounts 中的每个 account:
-
令 accountEmail 为 account 的
email成员的值。 -
如果 accountEmail 与 email 不区分大小写地相等,则返回 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。 -
令 evt 为 json 的
issuance_token。 -
验证 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 不区分大小写地不相等,则判定验证失败。
-
否则,验证成功。
5. 安全与隐私注意事项
5.1. 隐私
5.1.1. 对颁发者实施盲化
EVP 的一个关键隐私目标是防止颁发者得知用户正在与哪个验证者交互。用户代理必须确保 Well-Known 获取和账户获取不会泄露验证者的
源。
颁发请求尽管携带凭据,但不得在请求标头中包含验证者的源
(例如,必须省略 Referer 或 Origin)。
5.1.2. 跟踪风险
由于颁发请求使用 Cookie,因此颁发者会得知用户处于活跃状态。但是,它只会在 用户主动选择其电子邮件进行验证时得知这一点,而这是一项由用户发起的操作。5.2. 安全
5.2.1. 重放攻击
呈现令牌(EVT+KB)通过密钥绑定 JWT 绑定到特定的nonce 和 audience(验证者
源)。
验证者必须验证:
-
audience与其源匹配。 -
nonce与其为表单呈现生成的 nonce 匹配。 -
exp声明尚未过期。
这可以防止攻击者截获 EVT+KB,并将其重放到另一个网站或不同的 上下文中。