본문으둜 κ±΄λ„ˆλ›°κΈ°

🌐 Email

λ‹€μŒμ€ NextAuth.js의 이메일 μ œκ³΅μžμ— λŒ€ν•œ κ°œμš”μž…λ‹ˆλ‹€.

κ°œμš”β€‹

이메일 μ œκ³΅μžλŠ” μ‚¬μš©μžκ°€ λ‘œκ·ΈμΈν•  수 μžˆλ„λ‘ "맀직 링크"λ₯Ό μ΄λ©”μΌλ‘œ μ „μ†‘ν•˜μ—¬ μΈμ¦ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ§ν¬λŠ” Slackκ³Ό 같은 μ„œλΉ„μŠ€μ—μ„œ 자주 μ‚¬μš©λ˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. 이메일을 톡해 λ‘œκ·ΈμΈν•  수 μžˆλŠ” κΈ°λŠ₯을 μΆ”κ°€ν•¨μœΌλ‘œμ¨ μ‚¬μš©μžκ°€ OAuth 계정에 μ ‘κ·Όν•  수 없을 λ•Œ λŒ€μ•ˆμ μΈ 둜그인 방법을 μ œκ³΅ν•©λ‹ˆλ‹€.

μž‘λ™ 방식​

초기 둜그인 μ‹œ 제곡된 이메일 μ£Όμ†Œλ‘œ 검증 토큰이 μ „μ†‘λ©λ‹ˆλ‹€. 기본적으둜 이 토큰은 24μ‹œκ°„ λ™μ•ˆ μœ νš¨ν•©λ‹ˆλ‹€. μ‚¬μš©μžκ°€ μ΄λ©”μΌμ˜ 링크λ₯Ό ν΄λ¦­ν•˜μ—¬ 검증 토큰을 μ‚¬μš©ν•  경우, μ‚¬μš©μž 계정이 μƒμ„±λ˜κ³  λ‘œκ·ΈμΈλ©λ‹ˆλ‹€. 이미 μ‘΄μž¬ν•˜λŠ” κ³„μ •μ˜ 이메일 μ£Όμ†Œλ₯Ό μ œκ³΅ν•˜λ©΄, ν•΄λ‹Ή 이메일과 μ—°κ²°λœ κ³„μ •μœΌλ‘œ λ‘œκ·ΈμΈν•  수 μžˆλŠ” 이메일이 μ „μ†‘λ©λ‹ˆλ‹€.

μ˜΅μ…˜β€‹

이메일 μ œκ³΅μžλŠ” κΈ°λ³Έ μ˜΅μ…˜ μ„ΈνŠΈλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€:

이 μ˜΅μ…˜λ“€μ€ μ‚¬μš©μžμ˜ ν•„μš”μ— 맞게 μž¬μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ꡬ성​

NextAuth.jsλŠ” 이메일을 HTTP λ˜λŠ” SMTPλ₯Ό 톡해 전솑할 수 μžˆμŠ΅λ‹ˆλ‹€.

HTTP​

HTTP 기반 이메일 제곡자 κ°€μ΄λ“œλ₯Ό μ°Έκ³ ν•˜μ‹­μ‹œμ˜€.

SMTP​

  1. NextAuth.jsλŠ” nodemailerλ₯Ό μ’…μ†μ„±μœΌλ‘œ ν¬ν•¨ν•˜κ³  μžˆμ§€ μ•ŠμœΌλ―€λ‘œ, 이메일 제곡자λ₯Ό μ‚¬μš©ν•˜λ €λ©΄ 직접 μ„€μΉ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ‹€μŒ λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€:
    npm install nodemailer
    # λ˜λŠ”
    yarn add nodemailer
  2. SMTP 계정이 ν•„μš”ν•©λ‹ˆλ‹€. κ°€λŠ₯ν•˜λ©΄ nodemailer와 ν˜Έν™˜λ˜λŠ” μ„œλΉ„μŠ€λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.
  3. SMTP μ„œλ²„ 연결을 κ΅¬μ„±ν•˜λŠ” 방법은 두 κ°€μ§€μž…λ‹ˆλ‹€:
    • μ—°κ²° λ¬Έμžμ—΄ μ‚¬μš©
    • ꡬ성 객체 μ‚¬μš©

μ—°κ²° λ¬Έμžμ—΄ μ‚¬μš© μ˜ˆμ‹œ: .env νŒŒμΌμ— λ‹€μŒμ„ μΆ”κ°€ν•©λ‹ˆλ‹€:

EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=noreply@example.com

λ‹€μŒκ³Ό 같이 이메일 제곡자λ₯Ό μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

import EmailProvider from "next-auth/providers/email";

providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],

ꡬ성 객체 μ‚¬μš© μ˜ˆμ‹œ: .env νŒŒμΌμ— λ‹€μŒμ„ μΆ”κ°€ν•©λ‹ˆλ‹€:

EMAIL_SERVER_USER=username
EMAIL_SERVER_PASSWORD=password
EMAIL_SERVER_HOST=smtp.example.com
EMAIL_SERVER_PORT=587
EMAIL_FROM=noreply@example.com

이제 이메일 제곡자 섀정을 NextAuth.js μ˜΅μ…˜ 객체에 μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

import EmailProvider from "next-auth/providers/email";

providers: [
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}),
],
  1. 이메일 검증 토큰을 μ €μž₯ν•˜κΈ° μœ„ν•΄ λ°μ΄ν„°λ² μ΄μŠ€ μ–΄λŒ‘ν„°λ₯Ό μ„€μ •ν•˜λŠ” 것을 μžŠμ§€ λ§ˆμ‹­μ‹œμ˜€.
  2. /api/auth/signinμ—μ„œ 이메일 μ£Όμ†Œλ‘œ λ‘œκ·ΈμΈν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‚¬μš©μžκ°€ 이메일 μ£Όμ†Œλ₯Ό κ²€μ¦ν•˜κΈ° μ „κΉŒμ§€λŠ” μ‚¬μš©μž 계정이 μƒμ„±λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 이미 계정과 μ—°κ²°λœ 이메일 μ£Όμ†Œλ₯Ό μ‚¬μš©ν•˜λŠ” 경우, μ‚¬μš©μžλŠ” μ΄λ©”μΌμ˜ 링크λ₯Ό 톡해 ν•΄λ‹Ή κ³„μ •μœΌλ‘œ λ‘œκ·ΈμΈν•˜κ²Œ λ©λ‹ˆλ‹€.

이메일 μ‚¬μš©μž μ •μ˜β€‹

sendVerificationRequest μ˜΅μ…˜μ„ μ‚¬μš©ν•˜μ—¬ μ „μ†‘λ˜λŠ” 둜그인 이메일을 μ™„μ „νžˆ μ‚¬μš©μž μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import EmailProvider from "next-auth/providers/email";

providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({ identifier: email, url, provider: { server, from } }) {
/* μ‚¬μš©μž μ •μ˜ ν•¨μˆ˜ */
},
}),
],

검증 토큰 μ‚¬μš©μž μ •μ˜β€‹

기본적으둜 λ¬΄μž‘μœ„ 검증 토큰이 μƒμ„±λ©λ‹ˆλ‹€. 이λ₯Ό μž¬μ •μ˜ν•˜λ €λ©΄ 제곡자 μ˜΅μ…˜μ—μ„œ generateVerificationToken λ©”μ„œλ“œλ₯Ό μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

providers: [
EmailProvider({
async generateVerificationToken() {
return "ABC123"; // μ‚¬μš©μž μ •μ˜ 검증 토큰
},
}),
],

이메일 μ£Όμ†Œ μ •κ·œν™”β€‹

NextAuth.jsλŠ” 기본적으둜 이메일 μ£Όμ†Œλ₯Ό μ •κ·œν™”ν•©λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ λŒ€μ†Œλ¬Έμžλ₯Ό κ΅¬λΆ„ν•˜μ§€ μ•ŠμœΌλ©°, μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ 리슀트의 두 번째 이메일 μ£Όμ†ŒλŠ” μ œκ±°ν•©λ‹ˆλ‹€. μ‚¬μš©μž μ •μ˜ μ •κ·œν™”λ₯Ό μ›ν•œλ‹€λ©΄ normalizeIdentifier λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ„Έμš”.

EmailProvider({
normalizeIdentifier(identifier: string): string {
let [local, domain] = identifier.toLowerCase().trim().split("@");
domain = domain.split(",")[0];
return `${local}@${domain}`;
},
}),

κΈ°μ‘΄ μ‚¬μš©μžμ—κ²Œ 맀직 링크 전솑​

κΈ°μ‘΄ μ‚¬μš©μžμ—κ²Œλ§Œ 맀직 둜그인 링크λ₯Ό μ „μ†‘ν•˜λ„λ‘ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 이메일을 가져와 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ ν•΄λ‹Ή 이메일이 "User" μ»¬λ ‰μ…˜μ— μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•˜μ„Έμš”.

import User from "../../../models/User";
import db from "../../../utils/db";

callbacks: {
async signIn({ user, account, email }) {
await db.connect();
const userExists = await User.findOne({
email: user.email, // μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 이메일
});
if (userExists) {
return true; // 이메일이 μ‘΄μž¬ν•˜λ©΄ 맀직 둜그인 링크 전솑
} else {
return "/register"; // μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©΄ 등둝 νŽ˜μ΄μ§€λ‘œ 이동
}
},
},

μ΄λ ‡κ²Œ μ„€μ •ν•˜λ©΄ 이메일 제곡자λ₯Ό 톡해 λ‘œκ·ΈμΈν•  수 있으며, κΈ°μ‘΄ μ‚¬μš©μžμ—κ²Œλ§Œ 맀직 링크λ₯Ό 전솑할 수 μžˆμŠ΅λ‹ˆλ‹€.