서류 없이 5분 만에 Next.js SMS 휴대폰 본인인증 구현하기

2026년 4월 20일6분 소요

NEXTJS-SMS-AUTH

사이드 프로젝트에 SMS 인증을 붙이려다 포기한 적 있으신가요?

웹 서비스나 앱을 개발하다 보면 사용자 검증, 스팸 가입 방지 등을 위해 SMS 휴대폰 본인인증이 필수적인 순간이 옵니다. 하지만 기존 통신사나 대형 결제대행사(PG)를 통해 SMS 인증 API를 연동하려면 다음과 같은 험난한 과정을 거쳐야 합니다.

  • 복잡한 서류 제출: 사업자등록증, 통신서비스 이용증명원 등
  • 발신번호 사전등록: 법적으로 강제되는 복잡한 절차
  • 비싼 단가와 초기 세팅비: 건당 30~50원에 달하는 부담스러운 비용

사업자등록증이 없는 학생 개발자, 프리랜서 1인 개발자, 혹은 빠르게 MVP(최소 기능 제품)를 검증해야 하는 초기 스타트업에게는 이러한 절차가 프로젝트의 진행을 가로막는 큰 장벽이 됩니다.

하지만 걱정하지 마세요! **이지어스(EasyAuth)**를 활용하면 서류 제출이나 발신번호 사전등록 없이, 가입 후 5분 만에 Next.js 프로젝트에 SMS 인증을 구현할 수 있습니다.

이 글에서는 Next.js 14+ (App Router) 환경에서 EasyAuth를 이용해 초간단 SMS 본인인증 시스템을 구축하는 방법을 단계별로 알아봅니다.


이지어스(EasyAuth)란?

EasyAuth는 개발자를 위해 탄생한 초간단 SMS 인증 API 서비스입니다.

  • 서류 완전 면제: 사업자등록증명원 등 일체의 서류가 필요 없습니다.
  • 자동 발신번호: 귀찮은 대표번호 사전등록 없이 즉시 발송 가능합니다.
  • 합리적인 요금: 건당 1525원으로 기존(3050원) 대비 최대 50% 저렴합니다.
  • 간결한 API 구조: POST /send, POST /verify 단 두 개의 엔드포인트로 모든 것이 끝납니다.

지금 바로 회원가입하면 무료 테스트 10건이 제공되므로 이 튜토리얼을 따라 하기에 안성맞춤입니다.


1단계: 프로젝트 환경 설정 및 API 키 발급

먼저 Next.js 프로젝트를 준비합니다. 터미널을 열고 다음 명령어를 실행하여 새로운 프로젝트를 생성합니다.

npx create-next-app@latest sms-auth-demo

설치 과정에서 TypeScript, Tailwind CSS, App Router를 사용하도록 선택해주세요.

이제 EasyAuth 홈페이지에 가입하여 API 키를 발급받습니다. 발급받은 키를 프로젝트 루트의 .env.local 파일에 저장합니다.

EASYAUTH_API_KEY=your_easyauth_api_key_here

2단계: 백엔드 API 라우트 구현 (Next.js Route Handlers)

Next.js의 App Router를 사용하여 두 개의 API 엔드포인트를 만듭니다. 하나는 인증번호를 발송하는 라우트이고, 다른 하나는 사용자가 입력한 번호를 검증하는 라우트입니다. EasyAuth의 단순한 구조 덕분에 복잡한 DB 세팅 없이 바로 연동이 가능합니다.

1. 인증번호 발송 API (app/api/auth/send/route.ts)

프로젝트 내에 app/api/auth/send/route.ts 파일을 생성하고 다음 코드를 작성합니다.

import { NextResponse } from 'next/server';

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

    if (!phone) {
      return NextResponse.json({ error: '전화번호가 필요합니다.' }, { status: 400 });
    }

    // 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 }),
    });

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.message || '인증번호 발송 실패');
    }

    return NextResponse.json({ success: true, message: '인증번호가 발송되었습니다.' });
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}

2. 인증번호 검증 API (app/api/auth/verify/route.ts)

이어서 인증번호를 확인하는 검증 API를 만듭니다.

import { NextResponse } from 'next/server';

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

    if (!phone || !code) {
      return NextResponse.json({ error: '전화번호와 인증번호가 모두 필요합니다.' }, { status: 400 });
    }

    // 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, code }),
    });

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.message || '인증번호가 일치하지 않습니다.');
    }

    return NextResponse.json({ success: true, message: '인증이 완료되었습니다.' });
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 400 });
  }
}

정말 간단하죠? 백엔드 로직은 이것으로 끝입니다. DB에 난수를 저장하고 만료 시간을 계산하는 등의 번거로운 로직은 모두 EasyAuth 서버에서 알아서 처리해 줍니다.


3단계: 프론트엔드 UI 컴포넌트 구현

이제 사용자가 전화번호를 입력하고 인증번호를 받을 수 있는 화면을 만듭니다. app/page.tsx에 다음 코드를 작성합니다. Tailwind CSS를 사용하여 모바일 환경에서도 깔끔하게 보이도록 스타일링했습니다.

'use client';

import { useState } from 'react';

export default function SmsAuth() {
  const [phone, setPhone] = useState('');
  const [code, setCode] = useState('');
  const [step, setStep] = useState<1 | 2>(1);
  const [isLoading, setIsLoading] = useState(false);
  const [message, setMessage] = useState('');

  const handleSendCode = async () => {
    setIsLoading(true);
    setMessage('');
    
    try {
      const res = await fetch('/api/auth/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone: phone.replace(/[^0-9]/g, '') }),
      });
      
      const data = await res.json();
      
      if (res.ok) {
        setStep(2);
        setMessage('인증번호가 발송되었습니다. 휴대폰을 확인해주세요.');
      } else {
        setMessage(data.error);
      }
    } catch (error) {
      setMessage('서버 통신 오류가 발생했습니다.');
    } finally {
      setIsLoading(false);
    }
  };

  const handleVerifyCode = async () => {
    setIsLoading(true);
    setMessage('');
    
    try {
      const res = await fetch('/api/auth/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          phone: phone.replace(/[^0-9]/g, ''), 
          code 
        }),
      });
      
      const data = await res.json();
      
      if (res.ok) {
        setMessage('✅ 인증이 성공적으로 완료되었습니다!');
      } else {
        setMessage(`❌ ${data.error}`);
      }
    } catch (error) {
      setMessage('서버 통신 오류가 발생했습니다.');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <div>
        <h1>휴대폰 본인인증</h1>
        
        <div>
          <div>
            
              휴대폰 번호
            
            <div>
               setPhone(e.target.value)}
                disabled={step === 2}
                placeholder='01012345678'
                className='w-full rounded-lg border px-4 py-2 focus:border-blue-500 focus:outline-none disabled:bg-gray-100'
              /&gt;
              {step === 1 &amp;&amp; (
                
                  {isLoading ? '발송 중...' : '인증요청'}
                
              )}
            </div>
          </div>

          {step === 2 &amp;&amp; (
            <div>
              
                인증번호
              
              <div>
                 setCode(e.target.value)}
                  placeholder='6자리 숫자'
                  maxLength={6}
                  className='w-full rounded-lg border px-4 py-2 focus:border-blue-500 focus:outline-none'
                /&gt;
                
                  {isLoading ? '확인 중...' : '인증확인'}
                
              </div>
            </div>
          )}

          {message &amp;&amp; (
            <p>
              {message}
            </p>
          )}
        </div>
      </div>
    </div>
  );
}

실무 적용 시 꿀팁 (Best Practices)

이 코드를 실제 상용 서비스(MVP, 이커머스 등)에 배포하기 전에 몇 가지 보안 및 성능 최적화를 고려하면 더욱 완성도 높은 시스템을 만들 수 있습니다.

  1. 요청 속도 제한 (Rate Limiting 적용)
    악의적인 봇이 인증 API를 무단으로 반복 호출하여 막대한 SMS 비용이 청구되는 일(일명 'SMS 어뷰징')을 막아야 합니다. Next.js Middleware나 Upstash Redis 등을 활용해 IP 당 일일 발송 횟수를 3~5회로 제한하는 로직을 추가하세요.

  2. 입력 폼 데이터 정제 (Sanitization)
    사용자는 010-1234-5678처럼 하이픈(-)을 포함하거나, 공백을 섞어서 입력할 수 있습니다. 위 예제 코드처럼 replace(/[^0-9]/g, '') 정규식을 활용하여 서버에 전달하기 전에 숫자가 아닌 문자를 모두 제거해 주는 것이 안전합니다.

  3. 회원가입 여부 선제적 확인
    이미 가입된 유저인지 파악하는 로직을 API 라우트 앞단에 배치하세요. 중복된 번호라면 굳이 SMS를 발송하지 않고 에러를 반환함으로써 불필요한 발송 비용을 효과적으로 절약할 수 있습니다.


결론

지금까지 Next.js와 EasyAuth(이지어스)를 활용하여 단 5분 만에 서류 없이 깔끔한 SMS 모바일 인증 시스템을 구현하는 과정을 마쳤습니다.

기존의 무겁고 복잡한 본인인증 시스템(NICE, 다날 등)은 사업자등록증 제출과 까다로운 심사 절차 때문에 도입에 최소 수 주일이 소요됩니다. 하지만 EasyAuth를 사용하면 서류 제출이나 발신번호 사전등록이 전혀 필요 없기 때문에 1인 개발자의 토이 프로젝트, 사이드 프로젝트, 초기 스타트업의 MVP 테스트에 가장 최적화된 솔루션입니다.

뿐만 아니라 sendverify라는 가장 이상적인 두 개의 API 엔드포인트만으로 난수 생성 및 캐싱 처리까지 모두 해결해주므로 개발자는 본연의 비즈니스 로직에만 집중할 수 있습니다. 건당 15~25원이라는 매우 합리적인 비용 또한 놓칠 수 없는 장점입니다.

여러분의 다음 서비스에 빠르고 안전한 인증 시스템이 필요하다면, 가입 즉시 제공되는 10건의 무료 체험 혜택으로 이지어스를 지금 바로 연동해보세요!

SMS 인증을 쉽게 시작하세요

서류 없이 가입 즉시 API Key를 발급받고 바로 시작할 수 있습니다.
건당 25원, 가입 시 10건 무료!