Postmark プロバイダー
概要
Postmarkプロバイダーは、検証トークンを含むURLが記載された「マジックリンク」を送信するために電子メールを使用します。これによりサインインできます。
1つ以上のOAuthサービスに加えて電子メール経由でのサインインのサポートを追加することで、ユーザーがOAuthアカウントへのアクセスを失った場合(たとえば、ロックされたり削除されたりした場合)にサインインする方法が提供されます。
Postmarkプロバイダーは、1つ以上のOAuthプロバイダーと組み合わせて(または代わりに)使用できます。
仕組み
最初のサインイン時に、提供されたメールアドレスに**検証トークン**が送信されます。デフォルトでは、このトークンは24時間有効です。その時間内に検証トークンが使用された場合(つまり、電子メールのリンクをクリックした場合)、ユーザーのアカウントが作成され、サインインされます。
サインイン時に、既存のアカウントのメールアドレスが提供された場合、メールが送信され、メール内のリンクをたどると、そのメールアドレスに関連付けられているアカウントにサインインされます。
Postmarkプロバイダーは、JSON Web Tokenとデータベース管理セッションの両方で使用できますが、使用するには**データベースを構成する必要があります**。データベースを使用せずに電子メールサインインを有効にすることはできません。
構成
-
まず、Postmarkアカウントにドメインを追加する必要があります。これはPostmarkで必須であり、
from
プロバイダーオプションで使用するアドレスのドメインです。 -
次に、PostmarkダッシュボードでAPIキーを生成する必要があります。このAPIキーは、
AUTH_POSTMARK_KEY
環境変数として保存できます。
AUTH_POSTMARK_KEY=abc
環境変数をAUTH_POSTMARK_KEY
と名付けると、プロバイダーはそれを自動的に取得し、Auth.jsの構成オブジェクトをよりシンプルにできます。ただし、別の名前に変更する場合は、Auth.js構成でプロバイダーに手動で渡す必要があります。
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: "no-reply@company.com"
}),
],
})
-
電子メール検証トークンを保存するためのデータベースアダプターのいずれかを設定することを忘れないでください。
-
/api/auth/signin
でメールアドレスを使用したサインインプロセスを開始できるようになりました。
メールアドレスを初めて認証するまで、ユーザーアカウント(つまり、Users
テーブルのエントリ)はユーザーに対して作成されません。メールアドレスが既にあるアカウントに関連付けられている場合、ユーザーはマジックリンクメールのリンクをクリックして検証トークンを使用すると、そのアカウントにサインインします。
カスタマイズ
メール本文
Postmark()
にsendVerificationRequest
オプションとしてカスタム関数を渡すことで、送信されるサインインメールを完全にカスタマイズできます。
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()
)し、このメソッドで実際に送信するためにPostmarkへのネットワーク呼び出し(fetch()
)を行っていることに注意してください。
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で生成したい場合は、mjmlまたはreact-emailを確認してください。
検証トークン
デフォルトでは、ランダムな検証トークンが生成されます。オーバーライドする場合は、プロバイダーオプションでgenerateVerificationToken
メソッドを定義できます。
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
メソッドを使用して、独自の正規化を適用できます。次の例は、デフォルトの動作を示しています。
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")
// }
},
}),
],
})
複数のメールアドレスが渡された場合でも、必ず単一のメールアドレスを返すようにしてください。