[Express.js + TypeScript] 서류 없이 5분 만에 안전한 SMS 인증(OTP) API 구축하기 (Rate Limit 적용)
2026-05-26T01:02:06.673Z
[Express.js + TypeScript] 서류 없이 5분 만에 안전한 SMS 인증(OTP) API 구축하기
😱 SMS 인증, 시작부터 막히셨나요?
사이드 프로젝트나 MVP에 회원가입, 비밀번호 찾기 기능을 구현하다 보면 필연적으로 본인 인증이나 **전화번호 인증(OTP)**이 필요해집니다. 가벼운 마음으로 국내 SMS API 서비스들을 찾아보지만, 개발자들은 곧 다음과 같은 답답한 현실에 부딪히게 됩니다.
- 복잡한 서류 제출: 사업자등록증, 통신사 이용증명원 등 개인이나 예비창업자가 준비하기 힘든 서류 요구
- 발신번호 사전등록: 서류를 제출해도 심사까지 며칠씩 소요되는 발신번호 등록 절차
- 비싼 단가: 건당 30~50원에 달하는 부담스러운 문자 발송 비용
이제 막 아이디어를 테스트하려는 1인 개발자, 프리랜서, 또는 초기 스타트업에게 이런 진입 장벽은 너무 높습니다. 그렇다면 복잡한 서류 없이, 가입 즉시 SMS 인증을 구현할 수는 없을까요?
이 글에서는 서류 없이 5분 만에 연동 가능한 초간단 SMS API인 **EasyAuth(이지어스)**를 활용하여, Express.js와 TypeScript 환경에서 안전한 SMS OTP API를 구축하는 방법을 알아봅니다. 특히 실무 서비스에서 반드시 고려해야 하는 Rate Limiting(요청 제한) 적용 방법과 Redis 없이 상태를 관리하는 팁까지 함께 다루겠습니다.
💡 왜 EasyAuth인가요?
본격적인 코드 작성에 앞서, 우리가 사용할 EasyAuth의 핵심 장점을 가볍게 살펴보겠습니다.
- 서류 완전 불필요: 복잡한 증빙 서류 제출 없이 이메일 가입만으로 즉시 API Key 발급
- 자동 발신번호 제공: 대표번호 사전등록 절차 없이 시스템이 알아서 발송
- 합리적인 가격: 기존 대비 절반 수준인 건당 15~25원의 저렴한 단가
- 직관적인 API: 단 두 개의 엔드포인트(
POST /send,POST /verify)로 인증 끝 - 무료 체험: 가입 즉시 테스트용 10건 무료 제공
🛠️ Step 1: 프로젝트 설정 및 패키지 설치
먼저 안정적인 서버 환경을 위해 Express.js와 TypeScript 프로젝트를 세팅하고 필요한 패키지들을 설치합니다.
# 프로젝트 초기화
npm init -y
# 필수 패키지 설치
# express: 웹 서버 프레임워크
# axios: HTTP 클라이언트 (EasyAuth API 호출용)
# dotenv: 환경변수 관리
# express-rate-limit: 과도한 API 호출 방지
npm install express axios dotenv express-rate-limit
# TypeScript 및 타입 정의 파일 설치 (개발 환경용)
npm install -D typescript ts-node @types/express @types/node
# tsconfig.json 생성
npx tsc --init
설치가 완료되었다면 루트 디렉토리에 .env 파일을 생성하고 EasyAuth 홈페이지에서 발급받은 API 키를 입력합니다.
PORT=3000
EASYAUTH_API_KEY=your_easyauth_api_key_here
🛡️ Step 2: Rate Limiting으로 어뷰징(SMS Bombing) 방지
SMS 인증 API를 외부로 노출할 때 가장 주의해야 할 보안 이슈는 SMS Bombing(문자 폭탄) 공격입니다. 악의적인 봇이나 사용자가 무단으로 인증번호 발송 API를 짧은 시간 내에 수천 번 호출하면 심각한 금전적 피해가 발생할 수 있습니다.
이를 방지하기 위해 express-rate-limit 패키지를 사용하여 동일 IP에서 짧은 시간 내에 과도한 요청을 보내지 못하도록 방어벽을 설정해야 합니다.
🚀 Step 3: 전체 구현 코드 (Complete Code)
전통적인 SMS API를 사용하면 인증번호(OTP)를 생성하고 이를 Redis나 데이터베이스에 저장하여 타이머를 관리해야 하는 번거로움이 있습니다. 하지만 EasyAuth는 번호 생성과 검증, 유효시간 관리를 모두 자체적으로 처리해 줍니다. 개발자는 그저 /send와 /verify만 호출하면 됩니다.
다음은 Rate Limit과 EasyAuth 연동이 모두 적용된 server.ts 전체 코드입니다.
import express, { Request, Response } from 'express';
import rateLimit from 'express-rate-limit';
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.json());
// ==========================================
// 1. Rate Limiting 설정 (SMS Bombing 방지)
// ==========================================
const otpLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15분 단위로 체크
max: 5, // 15분 동안 IP당 최대 5회까지만 요청 허용
message: {
success: false,
error: '요청이 너무 많습니다. 잠시 후 다시 시도해주세요.'
},
standardHeaders: true, // `RateLimit-*` 헤더 반환
legacyHeaders: false, // `X-RateLimit-*` 헤더 비활성화
});
// EasyAuth API 기본 설정
const EASYAUTH_API_URL = 'https://api.easyauth.kr/v1';
const EASYAUTH_API_KEY = process.env.EASYAUTH_API_KEY;
// ==========================================
// 2. 인증번호 발송 API (/send)
// 보안을 위해 발송 엔드포인트에만 limiter를 적용합니다.
// ==========================================
app.post('/api/otp/send', otpLimiter, async (req: Request, res: Response) => {
const { phone } = req.body;
if (!phone) {
return res.status(400).json({ success: false, error: '전화번호가 필요합니다.' });
}
try {
// EasyAuth /send 엔드포인트 호출
await axios.post(`${EASYAUTH_API_URL}/send`,
{ phone },
{ headers: { Authorization: `Bearer ${EASYAUTH_API_KEY}` } }
);
res.status(200).json({ success: true, message: '인증번호가 발송되었습니다.' });
} catch (error: any) {
console.error('OTP Send Error:', error.response?.data || error.message);
res.status(500).json({ success: false, error: '인증번호 발송에 실패했습니다.' });
}
});
// ==========================================
// 3. 인증번호 검증 API (/verify)
// ==========================================
app.post('/api/otp/verify', async (req: Request, res: Response) => {
const { phone, code } = req.body;
if (!phone || !code) {
return res.status(400).json({ success: false, error: '전화번호와 인증번호가 필요합니다.' });
}
try {
// EasyAuth /verify 엔드포인트 호출
const response = await axios.post(`${EASYAUTH_API_URL}/verify`,
{ phone, code },
{ headers: { Authorization: `Bearer ${EASYAUTH_API_KEY}` } }
);
// 검증 성공 여부에 따른 분기 처리
if (response.data.isValid) {
res.status(200).json({ success: true, message: '인증이 성공적으로 완료되었습니다.' });
} else {
res.status(400).json({ success: false, error: '잘못된 인증번호이거나 만료되었습니다.' });
}
} catch (error: any) {
console.error('OTP Verify Error:', error.response?.data || error.message);
res.status(500).json({ success: false, error: '인증번호 검증 중 서버 오류가 발생했습니다.' });
}
});
// 서버 실행
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 SMS OTP Server is running on port ${PORT}`);
});
코드 실행해보기
터미널에서 아래 명령어를 통해 개발 서버를 실행할 수 있습니다.
npx ts-node server.ts
💡 실무 적용을 위한 Tips & Best Practices
-
Rate Limiting은 선택이 아닌 필수입니다 인증번호 발송 API는 로그인 없이 접근 가능한 Public API입니다. 따라서
express-rate-limit등을 통해 IP당 요청 횟수를 반드시 제한해야 금전적인 손실을 막을 수 있습니다. 필요하다면 클라이언트 측에서 reCAPTCHA 등을 추가로 도입하는 것도 좋은 방법입니다. -
환경변수 철저히 관리하기 API Key는 곧 비용과 직결되는 민감한 정보입니다. 절대로 GitHub 같은 퍼블릭 저장소에 소스코드를 올릴 때 하드코딩된 키가 노출되지 않도록
.env파일과.gitignore를 꼼꼼히 확인하세요. -
DB 없는 상태(Stateless) 관리의 편리함 기존에는 백엔드에서 6자리 난수를 생성하고, 이를 Redis에 유효시간(TTL)과 함께 저장한 뒤 문자를 발송해야 했습니다. 하지만 EasyAuth를 사용하면 백엔드에서 상태를 유지할 필요가 전혀 없습니다. 아키텍처가 단순해져 서버 부하가 줄고 개발 속도가 비약적으로 상승합니다.
🎉 마무리
지금까지 Express.js와 TypeScript 환경에서 Rate Limit을 적용한 안전하고 효율적인 SMS 인증 API를 구축해 보았습니다. 코드를 보시면 아시겠지만, 실제 비즈니스 로직을 구현하는 데 걸리는 시간은 5분이 채 되지 않습니다.
바쁜 개발자가 핵심 기능 구현 대신 복잡한 통신사 서류 작업과 심사 대기에 금쪽같은 시간을 낭비할 필요가 있을까요?
**[EasyAuth(이지어스)]**와 함께라면 더 이상 서류 스트레스에 시달리지 않아도 됩니다.
- 🚫 사업자등록증, 이용증명원 등 서류 제출 전면 폐지
- ⚡ 가입 후 5분 안에 실제 서비스 연동 및 테스트 완료
- 🤖 복잡한 심사 없는 자동 발신번호 제공
- 💰 기존 문자 서비스 대비 확연히 저렴한 건당 15~25원의 합리적 가격
토이 프로젝트, 이커머스 플랫폼, 또는 스타트업의 MVP를 가장 빠르고 경제적으로 검증하고 싶다면, 지금 바로 가입하고 10건 무료 혜택으로 여러분의 프로젝트에 날개를 달아주세요!
비트베이크에서 광고를 시작해보세요
광고 문의하기