[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건 무료 혜택으로 여러분의 프로젝트에 날개를 달아주세요!
Start advertising on Bitbake
Contact Us