Next.js와 tRPC로 구현한 이메일 인증 시스템: Resend 활용법
2025-02-04 10:07:38Next.js와 tRPC: 이메일 인증 시스템 구축하기
이메일 인증은 사용자의 신뢰성을 보장하는 중요한 단계입니다. 특히, Next.js와 tRPC를 활용한 프로젝트에서 이를 구현하는 방법을 알아보겠습니다. 우리는 Resend를 이용해 이메일 검증 절차를 간소화하고 더욱 직관적인 사용 환경을 제공합니다.
이메일 인증의 필요성과 구현 방안
사용자 등록 시 이메일 인증을 요구하는 것은 사용자 계정을 보호하고 데이터의 무단 액세스를 방지하는 핵심 수단이 됩니다. OTP(One Time Password)를 사용하여 이메일 주소를 검증하는 방식은 이러한 보안성을 높일 수 있는 효과적인 방법입니다.
Resend: 이메일 발송의 혁신
Resend는 이메일 발송을 단순하게 만들어 줍니다. 특히, 사용자 경험을 저해하지 않으면서도 필요한 인증을 손쉽게 처리할 수 있도록 돕습니다. Resend 설정은 쉽고 간단하며, 도메인 검증만으로 사용할 수 있습니다.
Resend 설정 방법
- Resend에 가입 후 도메인을 검증합니다.
- .env 파일에 Resend API Key를 추가합니다:
RESEND_API_KEY=your_resend_api_key EMAIL_FROM="John Doe <noreply@example.com>" - Resend Node.js SDK를 설치합니다:
yarn add resend - 유틸리티 파일을 생성하여 Resend 클라이언트를 export하여 사용합니다.
OTP 전송 기능 구현
이메일로 OTP를 전송하는 기능을 구현하여 사용자에게 전송할 OTP를 생성합니다. 이 OTP는 Redis에 저장되며 일정 시간이 지나면 자동으로 만료 처리됩니다.
OTP 전송 코드
// src/modules/auth/auth.service.ts
async sendOtpEmail(email: string) {
const otp = Math.floor(100000 + Math.random() * 900000);
try {
await redis.set(`otp:${email}`, otp, "EX", 5 * 60); // 5 minutes
await resend.emails.send({
from: process.env.EMAIL_FROM!,
to: email,
subject: "OTP to verify your email",
text: `Your OTP is ${otp}. It will expire in 5 minutes.`,
});
return { success: true };
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Something went wrong",
});
}
}
OTP 검증 및 자동 로그인
사용자가 입력한 OTP를 검증하고, 검증이 완료되면 사용자를 자동 로그인 시킵니다. 이는 사용자의 UX를 향상시킬 수 있는 방법 중 하나입니다.
OTP 검증 코드
// src/modules/auth/auth.service.ts
async verifyOtp(email: string, otp: string) {
try {
const savedOtp = await redis.get(`otp:${email}`);
if (savedOtp !== otp) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "OTP expired or invalid" });
}
await redis.del(`otp:${email}`);
const user = (await db.select().from(users).where(eq(users.email, email)).limit(1))[0];
if (!user) {
throw new TRPCError({ code: "NOT_FOUND", message: "User not found" });
}
await db.update(users).set({ emailVerified: true }).where(eq(users.id, user.id));
const accessToken = this.createAccessToken(user.id);
const refreshToken = this.createRefreshToken(user.id);
await redis.set(`refresh_token:${refreshToken}`, user.id, "EX", 7 * 24 * 60 * 60);
await redis.sadd(`refresh_tokens:${user.id}`, refreshToken);
await redis.expire(`refresh_tokens:${user.id}`, 7 * 24 * 60 * 60);
await redis.set(`user:${user.id}`, JSON.stringify(user), "EX", 7 * 24 * 60 * 60);
return { accessToken, refreshToken };
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Something went wrong",
});
}
}
프론트엔드: OTP 입력 방식
프론트엔드에서는 사용자가 회원가입 시 이메일을 입력한 후, OTP를 입력할 수 있는 추가 단계를 설정합니다. 이는 사용자에게 이메일이 전송되었다는 피드백과 동시에 보안 인증 절차를 완성하는 디테일을 제공합니다.
프런트엔드 구성
- OTP 입력을 위한 컴포넌트를 설정합니다.
- 사용자 입력과 이메일 전송 상태를 관리하는 로직을 추가합니다.
결론
Next.js와 tRPC, 그리고 Resend를 사용하여 이메일 인증 시스템을 구현하는 방법을 살펴보았습니다. 이 과정은 사용자에게 보다 안전하고 원활한 경험을 제공하여 신뢰를 구축할 수 있는 중요한 기반을 제공합니다.