Skip to content

OTP Verification Failures: 'token has expired' or 'otp_expired' errors

当用户尝试交换一次性密码(OTP)时,他们可能会遇到各种错误,提示令牌已无效。这些错误信息包括“令牌已过期或无效”、“邮箱链接无效或已过期”,或者在验证端点上收到 '403 Forbidden' 的 HTTP 响应。认证日志通常会显示特定的 otp_expired 错误代码。

🌐 When users attempt to exchange One-Time Passwords (OTPs), they may encounter various errors indicating that the token is no longer valid. These include messages like "token has expired or is invalid," "Email link is invalid or has expired," or they might receive '403 Forbidden' HTTP responses on the verification endpoint. Authentication logs often show specific otp_expired error codes.

什么是一次性密码(OTP)验证? #

🌐 What is OTP (one-time password) verification?

一次性密码(OTP)是为单次使用或有限时间生成的安全码。在认证流程中,特别是重置密码时,用户会请求一个 OTP,然后它会被发送到用户的邮箱或手机。用户必须在规定时间内把这个 OTP 提交回系统,以验证身份并进行如重置密码等操作。系统会使用类似 supabase.auth.verifyOtp({ email, token, type: 'recovery' }) 的函数来验证这个令牌。

🌐 An OTP, or One-Time Password, is a security code generated for a single use or a limited period. In authentication flows, especially for password resets, a user requests an OTP, which is then sent to their email or phone. The user must then provide this OTP back to the system within a set timeframe to verify their identity and proceed with actions like resetting a password. The system verifies this token using a function like supabase.auth.verifyOtp({ email, token, type: 'recovery' }).

了解错误 #

🌐 Understanding the errors

  • 403 禁止访问 HTTP 状态码: 这是一个标准的 HTTP 状态码,表示网页服务器理解了请求,但拒绝授权。在一次性密码 (OTP) 验证的情况下,如果 /verify 接口返回 403 禁止访问,通常意味着服务器拒绝了提交的 OTP 令牌,原因往往是它无效、已过期或已被使用。
  • otp_expired 错误: 这是身份验证服务报告的一个特定错误信息,明确指出提供的 OTP 令牌已过有效期,无法再使用。其他类似的信息,比如“令牌已过期或无效”或者“邮箱链接无效或已过期”,也属于令牌有效性问题的范畴。

根本原因:邮件预取 #

🌐 The root cause: Email prefetching

导致一次性密码(OTP)令牌在用户使用之前就显示过期或无效的最常见原因是电子邮件预取

🌐 The most common reason for OTP tokens appearing expired or invalid before a user can even use them is email prefetching.

  • 什么是电子邮件预取? 电子邮件预取是一种由电子邮件客户端或安全工具使用的机制,用来自动扫描,有时还会访问嵌入在邮件中的链接。这样做通常是为了检查恶意链接、更快显示内容,或者提供高级安全功能。
  • 安全工具的干扰方式: 组织常用的一些安全工具(例如,用于安全链接分析的工具)或者类似的安全扫描程序,会自动“点击”或访问邮件中的链接,以分析内容或检测威胁。如果你的密码重置邮件里包含一个确认链接(有时被称为“魔法链接”或一种访问后会自动验证令牌的链接),这些自动预取服务可能会在真正的用户操作之前就访问该链接,从而消耗掉一次性密码(OTP)令牌。这会让令牌在用户看来瞬间过期或无效,因为它已经被预取器使用或作废了。
  • 系统时钟问题: 有时候,用户设备的系统时钟与服务器时间不一致,可能会导致令牌看起来无效,因为系统认为令牌已过期,不过在现代同步系统中这种情况比较少见。

排查和找出问题 #

🌐 Troubleshooting and identifying the problem

要确定电子邮件预取或其他令牌有效性问题是否是根本原因,你可以在日志中关联事件:

🌐 To determine if email prefetching or another token validity issue is the root cause, you can correlate events across your logs:

  1. 监控 API 日志: 查看 /verify 端点的 '403 Forbidden' HTTP 状态码。这些响应直接表明验证尝试失败。
  2. 检查认证日志: 将 API 日志条目与你的认证日志交叉核对,看是否有 otp_expired 错误代码或类似“令牌无效/过期”的消息。
  3. 按时间和IP关联: 使用时间戳,如果可用的话,还可以利用API和认证日志中的IP地址,将特定的验证失败尝试与潜在的预取事件联系起来。如果你发现发送邮件后不久就出现快速的403错误或otp_expired消息,而用户合理点击链接的时间还没到,这很可能说明有预取行为。
  4. 确认验证方式: 确保你的应用使用了预期的 OTP 验证方式,例如 supabase.auth.verifyOtp,且类型正确(比如重置密码时使用 recovery)。虽然‘恢复’类型用于密码重置是正确的,但问题主要出在最初的链接访问,而不是最终的验证调用。

解决问题 #

🌐 Resolving the issue

解决这个问题通常涉及调整你的应用与电子邮件的互动方式以及令牌的处理方式:

🌐 Addressing this problem typically involves adjusting how your application interacts with email and how tokens are handled:

  1. 检查电子邮件模板: 仔细查看你的密码重置邮件模板。找出它们是否包含确认 URL(例如 {{ .ConfirmationURL }}),访问这些链接时可能会隐式地验证或使 OTP 令牌失效。
  2. 实现预取绕过/缓解
    • 查阅电子邮件服务文档: 查看你的电子邮件服务提供商或任何集成的安全工具的文档。许多服务提供防止或减轻自动预抓取的方法,比如特定的 HTML 属性(例如 rel="noreferrer noopener")或告知安全扫描器不要跟随链接的电子邮件头。
    • 延迟令牌失效: 考虑一下你的系统是否可以配置为仅在用户明确提交 OTP 令牌后才使其失效,而不是在最初访问确认链接时就失效。
    • 重新评估链接结构: 如果使用确认 URL,可以考虑其他设计,让链接只引导用户到页面输入 OTP,而不是直接使用它。
  3. 教育用户: 告诉用户不要转发密码重置邮件,应直接点击邮件中的链接。
  4. 考虑不使用直接链接的 OTP 流程: 如果预取仍然是一个持续问题,可以考虑一种 OTP 流程,让用户完全通过手动将邮件中的代码复制到你的应用中来操作,绕过任何可点击的确认 URL 来处理敏感部分。