Twilio 번호 구매 없이 연락처 인증 서비스 5분만에 구현하기
이번 포스팅에선 Twilio를 이용해 Node.js에서 개인 번호를 발급받지 않고, 핸드폰 번호 인증을 매우 간단하게 구현하는 방법에 대해 소개해드리겠습니다.
온라인 서비스를 확장하다보면 개인화된 경험을 위해 반드시 인증 서비스를 구현해야합니다. 구글,카톡으로 로그인하기 같은 OAuth도 있을거고, 이메일/패스워드 로그인도 있고, 아니면 연락처만 넣고 인증할 수도 있습니다. 운영하는 서비스에 맞게끔 잘 선택하면 되겠습니다.
저는 요즘 비밀번호를 수집하지 않는 것을 좋아합니다. 데이터베이스 유저 테이블에 password, password_salt 같은 항목을 넣기도 싫고, 보안에 더 신경을 써야할 것만 같은 느낌이 들기 때문입니다. 어렵진 않지만 굳이 암호화 로직도 추가해야하구요.
그리고 로그인 가능한 방법을 여러개 넣는 것도 좋아하지 않습니다. 구글로 로그인하기, 카톡으로 로그인하기, 이메일로 로그인하기, 연락처로 로그인하기.. 이런거 다 때려박는 서비스들이 가끔 있는데, 이러면 유저가 오랜만에 로그인하려고 하면 뭘로 했는지 기억이 안나서 로그인 시도를 여러번 해야하기 때문입니다. 일단 저부터가 뭘로 가입했었는지 기억이 안나서 중간에 앱 꺼버린 적이 생각보다 많았습니다.
시작하기
저희가 사용하려는 서비스는 Twilio Verify라는 기능입니다. 인증 1개당 $0.05입니다. 그리고 가입시 약 $15정도를 무료로 사용할 수 있도록 제공해줍니다. 한국어 번역은 지원되지 않습니다만 매우 쉽습니다.
회원가입 페이지로 접속하신 뒤 가입해주세요.

그 다음 입력한 이메일로 온 인증 메일을 통해 가입을 완료해주세요.

이메일 안의 링크를 눌렀다면, 다음은 연락처 인증입니다. 이 기능은 저희가 곧 이용할 기능이기도 합니다.

연락처는 1011112222 처럼 맨 앞 0은 빼고 넣으셔도 됩니다.
그러면 이렇게 인증 코드를 받을 수 있습니다. 근데 기존 기록을 보니 Mailchimp와 SendGrid도 Twilio를 통해 연락처 인증을 구현하고 있다는 사실을 알게 되었습니다.

가입을 완료했습니다.

서비스 이용을 위해 필수로 작성해야하는 설문조사를 해야합니다. 아무렇게나 작성하셔도 됩니다.
인증 시스템 구현하기
자 가입을 완료했으면 자동으로 대시보드로 이동하는데요, 왼쪽 메뉴 바에서 Verify -> Try it out 으로 이동합시다.

이 인증 서비스의 이름을 지어주세요. 이 이름은 나중에 인증 코드를 보낼 때 서비스의 이름으로 보여지게 됩니다. 인증 코드를 받는 유저가 헷갈리지 않도록 애플리케이션 이름과 일치시켜주시면 좋겠네요.

서비스까지 생성하셨다면 이제 거의 다 끝났습니다. 이제 서버 측 코드에서 API를 호출하기 위한 3개의 키를 가져오면 됩니다. 일단 왼쪽 상단 My First Twilio Account을 눌러 콘솔 메인 화면으로 이동해주세요.

저희에게 필요한 건 3가지 키입니다.
Account SIDAuth TokenService SID
Account SID와 Auth Token은 방금 방문한 콘솔 메인화면 하단에 있습니다.

마지막 Service SID는 Verify -> Services에 가면 가져올 수 있습니다.

서버측 코드 작성하기
코드를 작성하기 전에 먼저 필요한 npm 패키지들을 설치해주세요. 둘 다 타입스크립트 지원합니다.
yarn add twilio phone
# npm install twilio phone
저는 타입스크립트로 작성하겠습니다. 따로 모듈로 작성해두었구요, 이거 그냥 복붙해서 Secret 키만 바꾼다음 import 해서 사용하시면 바로 잘 작동할겁니다. 뭐 사실 너무 간단해서 코드 설명할 필요가 없습니다.
import twilio from "twilio";
export class Twilio {
private client: twilio.Twilio;
private accountSid = "AC9400af563ea46b42b3255f287abXXXXX";
private authToken = "65406c430c90d00268ef9bf0720XXXXX";
private verifyServiceSid = "VAaa47973652ccaabfc582ed8c1afXXXXX";
constructor() {
this.client = twilio(this.accountSid, this.authToken);
}
sendVerificationCode(options: { to: string }) {
return this.client.verify.v2
.services(this.verifyServiceSid)
.verifications.create({ to: options.to, channel: "sms" });
}
checkVerificationCode(options: { to: string; code: string }) {
return this.client.verify.v2
.services(this.verifyServiceSid)
.verificationChecks.create({
to: options.to,
code: options.code,
});
}
}
호출은 정말 너무나 간단합니다.
import express from "express";
import phone from "phone";
import { Twilio } from "./utils/sms";
const app = express();
app.use(express.json());
app.post("/send", async (req, res) => {
const body = req.body as { phone?: string };
if (!body.phone) {
throw new Error("400");
}
const phoneValidation = phone(body.phone, { country: "KOR" });
if (!phoneValidation.isValid) {
throw new Error("invalid format of the phone.");
}
const twilio = new Twilio();
const result = await twilio.sendVerificationCode({
to: phoneValidation.phoneNumber,
});
res.json({
success: true,
data: { result },
});
});
app.listen(3000, () => {
console.log("http://localhost:3000");
});
하나 짚어둘 부분이 있는데요, twilio 패키지에 연락처 정보를 넘길 때 항상 하이픈 없이 국가 코드를 같이 보내주어야 합니다. 예를들어 010-1111-2222 라는 한국 연락처가 있다고 했을 때 저 패키지 to 값에는 반드시 +821011112222 라고 넣어주어야 한다는 뜻입니다.
하지만 한국에서 일반적으로 연락처를 넣을 땐 01011112222 이렇게 11자리만 넣는게 모든 사람에게 익숙합니다. 아무도 +821011112222 이렇게 넣진 않습니다.
그래서 아까 phone 패키지를 같이 설치한건데요, 유저가 하이픈을 넣거나 국가코드를 넣지 않더라도 반드시 twilio가 원하는 포맷으로 만들어줍니다. 아예 안맞는 포맷이면 isValid 가 false가 되면서 유저에게 미리 에러 응답을 줄 수 있겠죠.
그래서 저 sendVerificationCode 함수에 대한 성공 응답은 이렇습니다.
{
"success": true,
"data": {
"result": {
"sid": "VEb7fbe1cfddfb5809d5e08748816XXXXX",
"serviceSid": "VAaa47973652ccaabfc582ed8c1afXXXXX",
"accountSid": "AC9400af563ea46b42b3255f287abXXXXX",
"to": "+821042730000",
"channel": "sms",
"status": "pending",
"valid": false,
"lookup": {
"carrier": null
},
"amount": null,
"payee": null,
"sendCodeAttempts": [
{
"attempt_sid": "VLb759dde6804ba896b9cdb44748dXXXXX",
"channel": "sms",
"time": "2023-02-04T03:36:03.000Z"
},
{
"attempt_sid": "VL6100bc660d2e825e3b8cfaacc15XXXXX",
"channel": "sms",
"time": "2023-02-04T03:38:05.798Z"
}
],
"dateCreated": "2023-02-04T03:36:03.000Z",
"dateUpdated": "2023-02-04T03:38:05.000Z",
"url": "https://verify.twilio.com/v2/Services/VAaa47973652ccaabfc582ed8c1af463ad/Verifications/VEb7fbe1cfddfb5809d5e0874881669f6a"
}
}
}
주목할 부분은 data.result.valid 값이 아직 인증 전이라 false 라는 것 말고는 없습니다.
아 그리고 data.result.sendCodeAttempts 부분을 보니 같은 번호에 대해 기록을 쌓아주는 것 같으니 너무 자주 요청을 보내지 못하도록 저 항목을 비교해서 좀 이따가 다시 시도하라는 응답을 우리 서버가 주도록 구현할 수도 있겠네요. 근데 구현하지 않더라도 아마 Twilio 내부적으로 자주 보내지 못하도록 Rate Limit이 있기는 할겁니다.
어쨌든 성공 응답을 받는다면 입력받은 연락처로 6자리 인증 코드가 발송됩니다.

아까 이야기했던 것 처럼 인증 코드를 보냈을 때 미리 지정해주었던 서비스 이름인 Phone Validation Code 가 잘 표시됩니다.
인증 쪽 코드도 똑같습니다. 연락처랑 코드 넣어주면 됩니다.
app.post("/verify", async (req, res) => {
const body = req.body as { phone?: string; code?: string };
if (!body.phone || !body.code) {
throw new Error("400");
}
const phoneValidation = phone(body.phone, { country: "KOR" });
if (!phoneValidation.isValid) {
throw new Error("invalid format of the phone.");
}
const twilio = new Twilio();
const result = await twilio.checkVerificationCode({
to: phoneValidation.phoneNumber,
code: body.code,
});
res.json({
success: true,
data: { result },
});
});
{
"success": true,
"data": {
"result": {
"sid": "VEb7fbe1cfddfb5809d5e08748816XXXXX",
"serviceSid": "VAaa47973652ccaabfc582ed8c1afXXXXX",
"accountSid": "AC9400af563ea46b42b3255f287abXXXXX",
"to": "+821042730000",
"channel": "sms",
"status": "approved",
"valid": true,
"amount": null,
"payee": null,
"dateCreated": "2023-02-04T03:36:03.000Z",
"dateUpdated": "2023-02-04T03:39:00.000Z"
}
}
}
data.valid는 true, data.status는 approved가 되면서 인증은 끝이 납니다. 직접 운영하시는 데이터베이스가 있다면 성공 요청 이후에 해당 유저 레코드를 업데이트 해주면 되겠네요.
마무리
외부 서비스를 이용하다보니 사진이 많았는데 사전 작업과 코드 정말 뭐 별거 없습니다. 키 받아와서 API 2개 사용한 거 그게 전부입니다.
이제 비밀번호 수집 없이 연락처만 받고 최대한 가볍게 인증을 구현하는 것도 가능하겠죠?
우리 모두 인증 때문에 스트레스 받지 맙시다.
같은 카테고리의 다른 글
[Nuxt 3] Composition API로 자동 스크롤링 기능 구현하기
이번 포스팅에서는 실시간 채팅 서비스에서 새로운 대화 내용이 추가되었을 때 자동으로 스크롤이 계속해서 아래로 내려가면서, 스크롤을 조작함에 따라 자동 스크롤이 활성화/비활성화되는 기능을 Vue 3에서 새로 추가된 Composition API를 통해 만들어볼겁니다.
[Nuxt 3] 사이드 프로젝트 만들기 - 개발 환경 설정편
저번 사이드 프로젝트 만들기 - 기획편의 다음 편입니다. 이번엔 nuxt3의 주요 변경사항 일부를 알아보고, 쾌적한 개발 환경을 위해 몇 가지 세팅을 해보도록 하겠습니다.
[Nuxt 3] 사이드 프로젝트 만들기 - 기획편
올해 첫 개발 관련 주제를 뭘로할까 고민하다가 사이드 프로젝트 아이디어가 떠올라서 그걸 같이 만들어볼까 합니다. 하지만 이미 잘 알고 있던 기술을 사용해서 만들면 재미없겠죠. 사이드 프로젝트는 역시 신기술을 이용해서 만드는게 가장 좋습니다. 나만의 프로젝트를 만들면서 최신 기술도 마음껏 써볼 수 있으니까요.
평생 무료로 반응형 이메일 템플릿 무한대로 만들기 - mjml.io
저는 이메일을 데스크톱과 모바일 환경에서 매일매일 확인합니다. 그런데 아직도 모바일 디스플레이에 최적화되지 않은 이메일을 받을 때가 많습니다.
평생 무료로 커스텀 이메일 사용하기
안녕하세요. 또 다시 찾아온 평생 무료 시리즈입니다. 저는 틈만나면 1인 사이드 프로젝트를 진행하기 때문에, 어떻게든 공짜로 서버를 돌리기 위해 온갖 노력을 하고 있습니다. 그래서 무료로 이용하는 방법에 관한 글을 몇 개 올렸는데 GA를 살펴보니 다른 주제보다 조회수가 높더군요. 역시 공짜가 좋네요.
클립보드 이미지를 1초만에 링크로 만드는 툴 개발하기
저는 이 블로그를 운영하면서 가장 귀찮은 일이 하나 있습니다. 바로 이미지 주소를 만드는 일인데요, 저는 @nuxt/content 모듈을 이용해 마크다운 포맷을 이용하는 정적 블로그를 운영 중이라 글 작성 중에 원격 이미지 주소를 삽입하는 기능을 사용하지 않습니다.
평생 무료인 모니터링 도구 10분만에 만들기
서버를 운영하다보면 예상치 못한 서버 다운이나 응답 속도 저하를 반드시 겪게 됩니다. 원인은 둘째 치구요. 근데 문제는 서버 장애를 원천 차단할 방법이 사실상 없기 때문에, 우리 개발자들이 24시간 눈을 뜨고 지켜볼 수 밖에 없겠습니다.
Vue.js로 크롬 확장 프로그램 만들기 강의 - 3부
이전 포스팅에서는 Vite을 이용해 크롬 확장 프로그램을 만들기 위한 기본적인 프로젝트 환경 설정까지 마쳤습니다. 본격적으로 Vue.js 코드를 작성해보도록 합시다.
정말 너무 쉬운 Docker
우리가 Docker를 사용해야하는 가장 큰 이유는, 어떤 컴퓨터에서든 똑같은 개발 환경을 보장해주기 떄문입니다. 로컬 컴퓨터에서 열심히 개발하고 AWS에 코드를 올렸는데, 에러를 마주하며 스트레스를 받았던 경험이 한 번쯤은 있을겁니다. 내 컴퓨터랑 클라우드 컴퓨터의 환경이 100% 똑같지 않기 때문이죠. 근데 이 어려움을 한 번에 해결해준다? 쓰지 말아야 할 이유가 없습니다.
웹소켓과 socket.io
예전에 회사 프로젝트를 진행할 때, 지도에 실시간으로 사용자의 위치를 보여주는 기능이 필요해서 socket.io 를 사용해서 구현했던 적이 있습니다.
Vue.js로 크롬 확장 프로그램 만들기 강의 - 2부
이전 포스팅에서 index.html과 manifest.json 파일을 이용해서 확장 프로그램을 개발자 모드로 실행시키는 것 까지 진행했습니다.
Vue.js로 크롬 확장 프로그램 만들기 강의 - 1부
제가 최근 우연히 크롬 확장 프로그램을 개발했는데, 이게 생각보다 꽤 괜찮은 시장이라는 걸 알게 되었습니다.
웹팩보다 100배 빠른 번들러, esbuild
이번 포스팅은 떠오르는 차세대 자바스크립트 번들러 esbuild에 대한 내용입니다. 작년 Github에서 떠오르는 번들링 프로젝트 중 1위를 차지했고, 오늘을 기준으로 20만개의 가까운 Github Star를 받았습니다.
Rollup.js - 플러그인으로 완성도를 높이다
지난 포스팅에서 rollup.js 를 이용해 두 개의 자바스크립트 파일을 하나로 묶고, rollup.config.js 파일을 구성해서 CLI가 아닌 스크립트로 설정 파일을 관리하는 것 까지 진행했습니다.
Rollup.js - 번들링, 파일을 하나로 합쳐보자
번들링 이라는 말을 프론트엔드 개발자라면 많이 들어보셨을겁니다. 번들링은, 파일을 하나로 묶는 것을 말합니다. 그럼 왜 굳이 파일을 하나로 묶어야 할까요? 바로 HTTP 통신의 특성 때문입니다.
코인 시세 1초만에 보는 크롬 확장 프로그램 만들기
가상화폐 거래소 API를 활용해 브라우저에서 단축키로 빠르게 코인 시세를 확인할 수 있는 툴을 크롬 확장 프로그램으로 만들어보았다.
평생 무료로 개인 블로그 운영하기
거의 대부분의 개발자들이 개인 블로그를 운영하라고 얘기한다. 나도 그렇게 생각한다. 왜냐면 분명히 내가 작성했던 코드인데도, 일주일만 지나도 기억이 안나기 때문이다. 그리고 웬만하면 공개해서 작성하라고 하고 싶다. 이미 우리는 누군가가 옛날에 썼던 글을 보고, 문제를 해결한 경험히 굉장히 많기 때문이다. 나는 이런 개발자들의 문화가 너무 좋다. 이런 개발자들의 문화가 다른 업종에도 접목된다면 정말 좋으련만.
Firebase를 대체할 오픈소스 프로젝트, Supabase
Supabase는 구글 Firebase를 엔터프라이즈 레벨에서도 사용 가능하도록 만든 오픈소스 프로젝트이다. 현재는 베타 서비스이다.