跳至內容
從 NextAuth.js v4 遷移?請閱讀 我們的遷移指南.

Mailgun 供應商

資源

概述

Mailgun 供應商使用電子郵件傳送「魔法連結」,其中包含可用於登入的驗證令牌 URL。

除了提供一個或多個 OAuth 服務外,新增支援透過電子郵件登入的方式,讓使用者在無法存取其 OAuth 帳戶時 (例如,如果該帳戶被鎖定或刪除) 也能登入。

Mailgun 供應商可以與一個或多個 OAuth 供應商搭配使用 (或取代它們)。

運作方式

在初始登入時,會將驗證令牌傳送到提供的電子郵件地址。預設情況下,此令牌的有效期限為 24 小時。如果在此時間內使用驗證令牌 (即,按一下電子郵件中的連結),則會為使用者建立帳戶,並且使用者會登入。

如果有人在登入時提供現有帳戶的電子郵件地址,則會傳送電子郵件,當他們點擊電子郵件中的連結時,他們會登入與該電子郵件地址相關聯的帳戶。

⚠️

Mailgun 供應商可以與 JSON Web Token 和資料庫管理的工作階段一起使用,但是您必須設定資料庫才能使用它。如果不使用資料庫,則無法啟用電子郵件登入。

設定

  1. 首先,您需要將您的網域新增到您的 Mailgun 帳戶。這是 Mailgun 的要求,也是您在 from 供應商選項中使用的位址的網域。

  2. 接下來,您必須在Mailgun 設定中產生 API 金鑰。您可以將此 API 金鑰儲存為 AUTH_MAILGUN_KEY 環境變數。

AUTH_MAILGUN_KEY=abc

如果您將您的環境變數命名為 AUTH_MAILGUN_KEY,供應商將會自動擷取它,並且您的 Auth.js 設定物件可以更簡單。但是,如果您想將它重新命名為其他名稱,您必須在 Auth.js 設定中手動將它傳遞到供應商中。

./auth.ts
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: ...,
  providers: [
    Mailgun({
      // If your environment variable is named differently than default
      apiKey: import.meta.env.AUTH_MAILGUN_KEY,
      from: "no-reply@company.com"
    }),
  ],
})
  1. 不要忘記設定一個資料庫轉接器來儲存電子郵件驗證令牌。

  2. 您現在可以在 /api/auth/signin 開始使用電子郵件地址登入的程序。

在使用者第一次驗證其電子郵件地址之前,不會為使用者建立使用者帳戶 (即 Users 資料表中的項目)。如果電子郵件地址已經與帳戶關聯,當使用者按一下魔法連結電子郵件中的連結並用完驗證令牌時,他們將登入該帳戶。

自訂

電子郵件內文

您可以透過將自訂函式作為 sendVerificationRequest 選項傳遞給 Mailgun(),來完全自訂傳送的登入電子郵件。

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

例如,以下顯示我們內建 sendVerificationRequest() 方法的原始碼。請注意,我們正在這裡的此方法中轉譯 HTML (html()) 並向 Mailgun 發出網路呼叫 (fetch()) 以實際執行傳送。

./lib/authSendRequest.ts
export async function sendVerificationRequest(params) {
  const { identifier: to, provider, url, theme } = params
  const { host } = new URL(url)
  const domain = provider.from.split("@").at(1)
 
  if (!domain) throw new Error("malformed Mailgun domain")
 
  const form = new FormData()
  form.append("from", `${provider.name} <${provider.from}>`)
  form.append("to", to)
  form.append("subject", `Sign in to ${host}`)
  form.append("html", html({ host, url, theme }))
  form.append("text", text({ host, url }))
 
  const res = await fetch(`https://api.mailgun.net/v3/${domain}/messages`, {
    method: "POST",
    headers: {
      Authorization: `Basic ${btoa(`api:${provider.apiKey}`)}`,
    },
    body: form,
  })
 
  if (!res.ok) throw new Error("Mailgun error: " + (await res.text()))
}
 
function html(params: { url: string; host: string; theme: Theme }) {
  const { url, host, theme } = params
 
  const escapedHost = host.replace(/\./g, "&#8203;.")
 
  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 Mailgun from "next-auth/providers/mailgun"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Mailgun({
      async generateVerificationToken() {
        return crypto.randomUUID()
      },
    }),
  ],
})

正規化電子郵件地址

預設情況下,Auth.js 將會正規化電子郵件地址。它將地址視為不區分大小寫 (這在技術上不符合 RFC 2821 規範,但在實務上,這會造成比解決的問題更多,例如從資料庫中依電子郵件查詢使用者時。) 並且也會移除任何以逗號分隔清單傳遞的次要電子郵件地址。您可以透過 Mailgun 供應商上的 normalizeIdentifier 方法來套用您自己的正規化。以下範例顯示預設行為

./auth.ts
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Mailgun({
      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