비트베이크

[Auth.js v5] Next.js에서 서류 없이 5분 만에 SMS 인증(OTP) 구현하기

2026-04-28T01:02:47.867Z

developer authentication modern

SMS 인증, 꼭 이렇게 복잡해야 할까요?

사이드 프로젝트나 스타트업 MVP를 개발하다 보면 전화번호 인증이 필요한 순간이 반드시 옵니다. 하지만 막상 기존 API를 연동하려고 하면 개발보다 행정 업무가 더 많습니다. 사업자등록증 제출, 발신번호 사전등록, 복잡한 심사 과정까지... 개발자의 흐름이 완전히 끊기게 되죠.

이 글에서는 Next.js App RouterAuth.js v5(구 NextAuth), 그리고 서류 제출 없이 즉시 사용할 수 있는 EasyAuth(이지어스) API를 조합해 단 5분 만에 SMS 인증을 구현하는 방법을 단계별로 소개합니다.


왜 Auth.js v5 + EasyAuth인가요?

  1. Auth.js v5: 최신 Next.js App Router와 완벽하게 호환됩니다. Credentials 프로바이더를 사용해 커스텀 OTP 로그인 로직을 매우 쉽게 연동할 수 있습니다.
  2. EasyAuth(이지어스):
    • 서류 0장: 사업자등록증, 이용증명원 제출 불필요
    • 즉시 시작: 가입 후 5분 내 연동 완료 (가입 시 테스트용 무료 10건 제공)
    • 자동 발신번호: 대표번호 사전등록 없이 즉시 발송 가능
    • 간편한 API: /send, /verify 두 개의 엔드포인트로 끝!

1단계: Auth.js v5 환경 설정

먼저 Next.js 프로젝트에 Auth.js 최신 버전을 설치합니다.

npm install next-auth@beta

프로젝트의 루트(또는 src 폴더)에 auth.ts 파일을 생성하고, EasyAuth의 검증 API(/verify)를 authorize 콜백에 연동합니다.

import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      name: "SMS OTP",
      credentials: {
        phone: { label: "Phone Number", type: "text" },
        code: { label: "OTP Code", type: "text" },
      },
      async authorize(credentials) {
        if (!credentials?.phone || !credentials?.code) return null;

        // EasyAuth API로 인증번호 검증
        const response = await fetch("https://api.easyauth.kr/verify", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${process.env.EASYAUTH_API_KEY}`
          },
          body: JSON.stringify({
            phone: credentials.phone,
            code: credentials.code
          })
        });

        const data = await response.json();

        // 검증 성공 시 세션에 저장될 사용자 객체 반환
        if (response.ok && data.success) {
          return { id: credentials.phone as string, name: credentials.phone as string };
        }

        return null; // 인증 실패 시
      }
    })
  ],
  pages: {
    signIn: "/login", // 커스텀 로그인 페이지 경로
  }
});

2단계: 인증번호 발송 API (Route Handler) 작성

클라이언트 사이드에서 직접 API 키를 노출하는 것은 보안상 매우 위험합니다. Next.js Route Handler를 생성해 서버에서 EasyAuth의 /send 엔드포인트를 호출하도록 구성합니다.

app/api/send-otp/route.ts 파일을 생성합니다.

import { NextResponse } from "next/server";

export async function POST(req: Request) {
  try {
    const { phone } = await req.json();

    // EasyAuth API로 인증번호 발송
    const response = await fetch("https://api.easyauth.kr/send", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${process.env.EASYAUTH_API_KEY}`
      },
      body: JSON.stringify({ phone })
    });

    if (!response.ok) throw new Error("발송 실패");

    return NextResponse.json({ success: true, message: "OTP sent" });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: "서버 에러가 발생했습니다." }, 
      { status: 500 }
    );
  }
}

3단계: 클라이언트 로그인 폼 (전체 완성 코드)

마지막으로 사용자가 전화번호를 입력하고, 수신한 인증번호를 제출하여 로그인할 수 있는 UI를 구성합니다.

app/login/page.tsx

"use client";

import { useState } from "react";
import { signIn } from "next-auth/react";

export default function LoginPage() {
  const [phone, setPhone] = useState("");
  const [code, setCode] = useState("");
  const [step, setStep] = useState<1 | 2>(1);

  // 1. 인증번호 발송 요청
  const handleSendCode = async () => {
    const res = await fetch("/api/send-otp", {
      method: "POST",
      body: JSON.stringify({ phone }),
    });
    
    if (res.ok) {
      alert("인증번호가 발송되었습니다!");
      setStep(2);
    } else {
      alert("발송에 실패했습니다. 다시 시도해주세요.");
    }
  };

  // 2. 인증번호 검증 및 NextAuth 로그인 처리
  const handleVerifyCode = async () => {
    const result = await signIn("credentials", {
      phone,
      code,
      redirect: true,
      redirectTo: "/dashboard", // 로그인 성공 후 이동할 경로
    });
  };

  return (
    <div>
      <h1>SMS 본인인증</h1>
      
      {step === 1 ? (
        <div>
           setPhone(e.target.value)} 
            placeholder="전화번호 (예: 01012345678)" 
            className="border p-3 rounded"
          /&gt;
          
            인증번호 받기
          
        </div>
      ) : (
        <div>
           setCode(e.target.value)} 
            placeholder="인증번호 6자리" 
            className="border p-3 rounded"
          /&gt;
          
            인증 완료 및 로그인
          
        </div>
      )}
    </div>
  );
}

💡 Tips & Best Practices

  • 보안 철저 (환경 변수 보호): EASYAUTH_API_KEY는 반드시 .env.local 파일에 서버사이드 전용으로만 보관하세요. NEXT_PUBLIC_ 접두사를 붙이면 클라이언트에 노출되므로 절대 주의해야 합니다.
  • 예외 처리 최적화: 실제 상용 서비스에 적용할 때는 유효하지 않은 전화번호 정규식 검사나 검증 실패 시의 명확한 에러 피드백을 추가하는 것이 좋습니다.

마무리: 서류 없는 가장 쉬운 SMS 인증

지금까지 최신 Auth.js v5와 Next.js App Router를 사용하여 깔끔하고 안전하게 SMS OTP 인증을 구현하는 방법을 살펴보았습니다.

기존 방식대로라면 사업자 인증 심사만 며칠을 기다려야 했겠지만, **EasyAuth(이지어스)**를 활용하면 이 모든 과정을 생략하고 단 5분 만에 핵심 비즈니스 로직 연동을 완료할 수 있습니다. 복잡한 절차 없이 오늘 당장 구현을 마쳐야 하는 프리랜서나 1인 개발자, 스타트업 개발자라면 가입 즉시 무료로 제공되는 10건의 크레딧으로 이 코드들을 테스트해 보세요!

비트베이크에서 광고를 시작해보세요

광고 문의하기

다른 글 보기

2026-06-04T01:04:15.823Z

The 2026 E-Commerce New Product Launch Survival Formula: Dominating Platform Search Rankings in 7 Days via Reward-Based Trials and Purchase Verification

2026-06-04T01:04:15.800Z

2026 이커머스 신제품 론칭 생존 공식: 리워드형 체험단과 구매 인증으로 7일 만에 플랫폼 검색 랭킹 장악하기

2026-06-01T01:01:58.264Z

Surviving the 2026 Cookieless Era for B2C: Building Zero-Party Data with Reward-Based Quiz Marketing

2026-06-01T01:01:58.231Z

2026 쿠키리스 시대의 B2C 생존법: 리워드 기반 퀴즈 마케팅으로 제로파티 데이터 구축하기

서비스

피드자주 묻는 질문고객센터

문의

비트베이크

레임스튜디오 | 사업자 등록번호 : 542-40-01042

경기도 남양주시 와부읍 수례로 116번길 16, 4층 402-제이270호

트위터인스타그램네이버 블로그