コンテンツにスキップ
NextAuth.js v4 からの移行ですか? こちらをご覧ください 移行ガイド.

Nodemailer プロバイダー

概要

Nodemailer プロバイダーは、メールを使用して、検証トークンを含む URL を持つ「マジックリンク」を送信し、サインインに使用できるようにします。

1つ以上の OAuth サービスに加えて、メール経由でのサインインのサポートを追加することで、ユーザーが OAuth アカウントへのアクセスを失った場合(例:ロックされたり削除されたりした場合)にサインインする方法を提供します。

Nodemailer プロバイダーは、1つ以上の OAuth プロバイダーと組み合わせて(またはその代わりに)使用できます。

仕組み

最初のサインイン時に、検証トークンが提供されたメールアドレスに送信されます。デフォルトでは、このトークンは 24 時間有効です。その時間内に検証トークンが使用された場合(つまり、メールのリンクをクリックした場合)、ユーザーのアカウントが作成され、サインインされます。

サインイン時に既存のアカウントのメールアドレスを提供した場合、メールが送信され、メールのリンクをクリックすると、そのメールアドレスに関連付けられたアカウントにサインインします。

⚠️

Nodemailer プロバイダーは、JSON Web Token とデータベース管理セッションの両方で使用できますが、使用するにはデータベースを設定する必要があります。データベースを使用せずにメールサインインを有効にすることはできません。

設定

  1. Auth.js には、依存関係として nodemailer は含まれていないため、Nodemailer プロバイダーを使用する場合は、自分でインストールする必要があります。
npm install nodemailer
  1. SMTP アカウントが必要です。できれば、nodemailer で動作することが知られているサービスのいずれかを使用してください。Nodemailer はその他のトランスポートでも動作しますが、HTTP ベースのメールサービスを使用する場合は、ResendSendgrid のような、それらのために設計された他の Auth.js プロバイダーを使用することをお勧めします。

  2. SMTP サーバー接続を構成する方法は 2 つあります。

接続文字列または nodemailer 設定オブジェクトのいずれかを使用できます。

.env
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=noreply@example.com
./auth.ts
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: ...,
  providers: [
    Nodemailer({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
    }),
  ],
})
  1. メール検証トークンを保存するためのデータベース アダプターのいずれかを設定することを忘れないでください。

  2. /api/auth/signin でメールアドレスを使用してサインインプロセスを開始できます。

ユーザーアカウント(つまり、Users テーブルのエントリ)は、ユーザーが最初にメールアドレスを確認するまで作成されません。メールアドレスがすでにアカウントに関連付けられている場合は、ユーザーがメールのリンクを使用すると、そのアカウントにサインインします。

カスタマイズ

メール本文

Nodemailer()sendVerificationRequest オプションとしてカスタム関数を渡すことで、送信されるサインインメールを完全にカスタマイズできます。

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

例として、以下に組み込みの sendVerificationRequest() メソッドのソースを示します。HTML をレンダリング (html()) し、メールプロバイダーにネットワーク呼び出し (transport.sendMail()) を行い、このメソッドで実際に送信を実行していることに注意してください。

import { createTransport } from "nodemailer"
 
export async function sendVerificationRequest(params) {
  const { identifier, url, provider, theme } = params
  const { host } = new URL(url)
  // NOTE: You are not required to use `nodemailer`, use whatever you want.
  const transport = createTransport(provider.server)
  const result = await transport.sendMail({
    to: identifier,
    from: provider.from,
    subject: `Sign in to ${host}`,
    text: text({ url, host }),
    html: html({ url, host, theme }),
  })
  const failed = result.rejected.concat(result.pending).filter(Boolean)
  if (failed.length) {
    throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
  }
}
 
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 で生成したい場合は、mjml または react-email を参照してください。

検証トークン

デフォルトでは、ランダムな検証トークンを生成しています。オーバーライドする場合は、プロバイダーオプションで generateVerificationToken メソッドを定義できます

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

メールアドレスの正規化

Auth.js はデフォルトでメールアドレスを正規化します。アドレスを大文字小文字を区別しないものとして扱い(これは技術的にはRFC 2821 仕様に準拠していませんが、実際には、データベースからメールでユーザーを検索する場合など、解決するよりも多くの問題を引き起こします。)、コンマ区切りのリストとして渡された可能性のあるセカンダリメールアドレスも削除します。Nodemailer プロバイダーの normalizeIdentifier メソッドを使用して、独自の正規化を適用できます。次の例は、デフォルトの動作を示しています。

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