跳至内容
从 NextAuth.js v4 迁移?阅读 我们的迁移指南.

Postmark 提供商

概述

Postmark 提供商使用电子邮件发送“魔术链接”,其中包含用于验证的 URL 和令牌,可用于登录。

除了一个或多个 OAuth 服务之外,还添加了对通过电子邮件登录的支持,为用户提供了一种在他们丢失 OAuth 帐户访问权限(例如,如果帐户被锁定或删除)时登录的方法。

Postmark 提供商可以与一个或多个 OAuth 提供商一起使用(或代替一个或多个 OAuth 提供商)。

工作原理

在初始登录时,将向提供的电子邮件地址发送**验证令牌**。默认情况下,此令牌有效期为 24 小时。如果验证令牌在该时间段内使用(即,通过单击电子邮件中的链接),则会为用户创建帐户,并且他们会登录。

如果有人在登录时提供了现有帐户的电子邮件地址,则会发送一封电子邮件,并在他们点击电子邮件中的链接并使用验证令牌时登录到与该电子邮件地址关联的帐户。

⚠️

Postmark 提供商可以与 JSON Web Token 和数据库管理的会话一起使用,但是**必须配置数据库**才能使用它。如果不用数据库,则无法启用电子邮件登录。

配置

  1. 首先,您需要将您的域添加到您的 Postmark 帐户。这是 Postmark 所要求的,也是您在from 提供商选项中使用的地址的域。

  2. 接下来,您需要在Postmark 仪表板中生成 API 密钥。您可以将此 API 密钥保存为AUTH_POSTMARK_KEY环境变量。

AUTH_POSTMARK_KEY=abc

如果您将环境变量命名为AUTH_POSTMARK_KEY,则提供商会自动获取它,您的 Auth.js 配置对象可以更简单。但是,如果您想将其重命名为其他名称,则必须在 Auth.js 配置中手动将其传递给提供商。

./auth.ts
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: ...,
  providers: [
    Postmark({
      // If your environment variable is named differently than default
      apiKey: AUTH_POSTMARK_KEY,
      from: "[email protected]"
    }),
  ],
})
  1. 不要忘记为存储电子邮件验证令牌设置一个数据库适配器

  2. 您现在可以在/api/auth/signin使用电子邮件地址启动登录流程。

在用户首次验证电子邮件地址之前,不会为用户创建用户帐户(即Users表中的条目)。如果电子邮件地址已与帐户关联,则当用户单击魔术链接电子邮件中的链接并使用验证令牌时,他们将登录到该帐户。

自定义

邮件正文

您可以通过将自定义函数作为sendVerificationRequest选项传递给Postmark(),完全自定义发送的登录电子邮件。

./auth.ts
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Postmark({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
      sendVerificationRequest({
        identifier: email,
        url,
        provider: { server, from },
      }) {
        // your function
      },
    }),
  ],
})

例如,以下显示了内置sendVerificationRequest()方法的源代码。请注意,我们正在渲染 HTML(html())并进行网络调用(fetch())到 Postmark 以在此方法中实际进行发送。

./lib/authSendRequest.ts
export async function sendVerificationRequest(params) {
  const { identifier: to, provider, url, theme } = params
  const { host } = new URL(url)
  const res = await fetch("https://api.postmark.com/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${provider.apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: provider.from,
      to,
      subject: `Sign in to ${host}`,
      html: html({ url, host, theme }),
      text: text({ url, host }),
    }),
  })
 
  if (!res.ok)
    throw new Error("Postmark error: " + JSON.stringify(await res.json()))
}
 
function html(params: { url: string; host: string; theme: Theme }) {
  const { url, host, theme } = params
 
  const escapedHost = host.replace(/\./g, "​.")
 
  const brandColor = theme.brandColor || "#346df1"
  const color = {
    background: "#f9f9f9",
    text: "#444",
    mainBackground: "#fff",
    buttonBackground: brandColor,
    buttonBorder: brandColor,
    buttonText: theme.buttonText || "#fff",
  }
 
  return `
<body style="background: ${color.background};">
  <table width="100%" border="0" cellspacing="20" cellpadding="0"
    style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
    <tr>
      <td align="center"
        style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
        Sign in to <strong>${escapedHost}</strong>
      </td>
    </tr>
    <tr>
      <td align="center" style="padding: 20px 0;">
        <table border="0" cellspacing="0" cellpadding="0">
          <tr>
            <td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
                target="_blank"
                style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
                in</a></td>
          </tr>
        </table>
      </td>
    </tr>
    <tr>
      <td align="center"
        style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
        If you did not request this email you can safely ignore it.
      </td>
    </tr>
  </table>
</body>
`
}
 
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
  return `Sign in to ${host}\n${url}\n\n`
}

如果您想使用 React 生成外观漂亮的电子邮件,这些电子邮件与许多电子邮件客户端兼容,请查看mjmlreact-email

验证令牌

默认情况下,我们生成一个随机验证令牌。如果您想覆盖它,可以在提供商选项中定义一个generateVerificationToken方法

./auth.ts
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Postmark({
      async generateVerificationToken() {
        return crypto.randomUUID()
      },
    }),
  ],
})

规范化电子邮件地址

默认情况下,Auth.js 会规范化电子邮件地址。它将地址视为不区分大小写(这在技术上不符合RFC 2821 规范,但在实践中,这会导致比解决问题更多的问题,例如,从数据库中按电子邮件查找用户时)。此外,还会删除可能作为逗号分隔列表传入的任何辅助电子邮件地址。您可以通过Postmark提供商上的normalizeIdentifier方法应用您自己的规范化。以下示例显示了默认行为

./auth.ts
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Postmark({
      normalizeIdentifier(identifier: string): string {
        // Get the first two elements only,
        // separated by `@` from user input.
        let [local, domain] = identifier.toLowerCase().trim().split("@")
        // The part before "@" can contain a ","
        // but we remove it on the domain part
        domain = domain.split(",")[0]
        return `${local}@${domain}`
 
        // You can also throw an error, which will redirect the user
        // to the sign-in page with error=EmailSignin in the URL
        // if (identifier.split("@").length > 2) {
        //   throw new Error("Only one email allowed")
        // }
      },
    }),
  ],
})
⚠️

始终确保这会返回单个电子邮件地址,即使传入多个地址。

Auth.js © Balázs Orbán 和团队 -2024