为什么 Auth0 email_verified 在我的 Access Token 中缺失

发布: (2026年2月19日 GMT+8 09:35)
4 分钟阅读
原文: Dev.to

Source: Dev.to

问题

Spring Boot 过滤器在用户已在 Auth0 仪表盘标记为 Verified 的情况下仍然拒绝了他们。

Boolean emailVerified = jwt.getClaimAsBoolean("email_verified");
if (emailVerified == null || !emailVerified) {
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.getWriter().write("{\"error\":\"Email not verified\"}");
    return;
}

日志输出:

User email not verified: sub=auth0|67c6a695657d0f4f7ac8736f

这些用户 已经 验证过了。原因是:Auth0 默认 在访问令牌中包含 email_verified——只在 ID 令牌中包含。

在解码访问令牌时,你可能会看到:

{
  "iss": "https://your-tenant.auth0.com/",
  "sub": "auth0|67c6a695657d0f4f7ac8736f",
  "aud": ["https://your-api/"],
  "iat": 1767421775,
  "exp": 1767508175,
  "scope": "openid profile email"
}

请注意缺少 email_verified 声明,即使已经请求了 email 范围。

Source:

email_verified 添加到访问令牌

使用登录后操作

  1. 在 Auth0 仪表盘中进入 Actions → Flows → Login
  2. 添加一个新 Action 并粘贴以下代码:
exports.onExecutePostLogin = async (event, api) => {
  // Add email_verified to access token for API validation
  api.accessToken.setCustomClaim('email_verified', event.user.email_verified);
};
  1. 部署该 Action 并将其拖入 Login 流程。

部署后,访问令牌将包含:

{
  "email_verified": true,
  "iss": "https://your-tenant.auth0.com/",
  "sub": "auth0|67c6a695657d0f4f7ac8736f",
  ...
}

为什么这样做有效

  • ID 令牌 用于客户端(前端)传递身份信息。
  • 访问令牌 用于 API(后端)授权请求。
  • Auth0 的默认理念是将身份相关的声明从访问令牌中剔除,但许多 API 需要这些信息。通过 Action 添加声明是推荐的做法。

替代方案:查询 UserInfo 端点

如果无法使用 Actions,当缺少该声明时,您可以从 /userinfo 端点获取该声明:

if (emailVerified == null) {
    String userinfoUrl = auth0Issuer + "userinfo";
    // Perform GET with Bearer token
    // Parse JSON response for "email_verified"
}

注意: 这会为每个请求增加额外的延迟,因此通常更倾向于使用 Action 方法。

社交登录

对于社交连接(Google、GitHub 等),Auth0 会自动设置 email_verified: true,因为提供者已经验证了电子邮件。您可以跳过对非数据库连接的检查:

String subject = jwt.getSubject();
boolean isDatabaseConnection = subject != null && subject.startsWith("auth0|");

if (isDatabaseConnection) {
    // Only check email_verified for username/password users
}

完整操作示例(包括可选的电子邮件重新发送)

exports.onExecutePostLogin = async (event, api) => {
  // Always include email_verified in access token
  api.accessToken.setCustomClaim('email_verified', event.user.email_verified);

  // Auto‑resend verification email for unverified users (rate‑limited)
  if (!event.user.email_verified) {
    const lastSent = event.user.user_metadata?.verification_email_last_sent;
    const now = Date.now();
    const ONE_HOUR = 60 * 60 * 1000;

    if (!lastSent || (now - lastSent) > ONE_HOUR) {
      // Trigger verification email via Management API
      // (see Auth0 docs for Management API setup)

      api.user.setUserMetadata('verification_email_last_sent', now);
    }
  }
};

要点

  • ID 令牌 ≠ Access 令牌 – 它们的用途不同,包含的声明也不同。
  • 使用 Auth0 Actions 向访问令牌添加所需的自定义声明。
  • 验证实际的令牌内容(例如使用 jwt.io),而不要仅仅依赖仪表板 UI。
0 浏览
Back to Blog

相关文章

阅读更多 »