본문 바로가기

프로그래밍/Node.js

[Node.js] Node.js로 SMS 인증번호 시스템 구현하기

반응형

최근 대부분의 서비스에서 회원가입 또는 로그인을 할 때, SMS 인증번호를 통한 Auth Check를 하는 경우가 많다.
(물론 최근에는 카카오톡 메시지로 인증번호를 전달하는 경우도 많아졌지만)

 

대부분의 회사에서는 SMS 인증과 관련된 API가 구성되어 있다.

만약, 개별 프로젝트를 진행 중인 상황에서 SMS 인증번호를 통한 Auth 로직을 구성해야 한다면 어떻게 해야할까?

 

네이버의 SMS 인증번호 사례 (문제시 삭제)

SMS 전송 API의 선택

SMS 인증번호 로직을 구현하기 위한 1단계는 문자 전송 API를 선택하는 것이다.

적당한 구글링을 통해 각자의 필요나 상황에 따라 가장 적합한 SMS 전송 API를 찾을 수 있을 것이다.

(만약, E-Mail 인증을 택한다면, E-Mail 인증 서비스를 제공해주는 API도 충분히 찾을 수 있다.)

 

SMS API 중 검색 결과로 많이 나오는 서비스는 Twilio(링크)로 알고 있다.

아무래도 이 글을 읽는 한국어가 편한 개발자(나를 포함한)는 영문 기반의 Twilio보다는 한글로 충분한 설명이 제공되는 Naver Cloud Platform의 메시지 알림 서비스 SENS(링크)가 더 편할 수 있다.

 

SMS 인증 API의 구성

문자 메시지를 전송해줄 수 있는 외부 API 서비스를 선택하고, 해당 서비스의 사용법(메시지 발송이니 POST 요청일 것)을 확인했다면, 현재 구성하고 있는 Node.js 서버에서 어떻게 SMS 인증번호를 통한 인증 서비스를 구현할 것인지 정해야 한다.

0. 인증번호의 저장 방법

서버에서 생성한 인증번호를 어떻게 관리할 것인가에 대한 문제가 개별 API의 구성보다 앞서서 나와야하는 문제가 아닐까?

인증번호는 다음과 같은 특징을 갖고 있다.

 

  • 인증번호는 저장의 대상이지만, 영구적인 저장의 대상이 아니며 즉시성(Immediately)의 성격을 갖고 있다.
  • 인증번호는 사용자의 요청에 의해 지속적으로 변경될 수 있다.
  • 인증번호와 휴대폰 번호는 1:1의 관계를 갖고 있다.

위와 같은 특성들 때문에 인증번호를 DB에 저장할 필요성을 느끼지 못하였다. 심지어는 NoSQL을 사용하는 것도 Resource를 낭비하는 것일 수도 있다고 판단하였다.

SMS 인증번호의 경우 Re-usable하지 않은 값이다. 즉, 특정 사용자를 검증하기 위한 1회성의 용도 외에는 가치가 없는 정보이며, 대부분의 경우 인증번호 발송 이후 짧게는 1분에서 길어도 3분 이내로 해당 인증번호의 Valid Time을 설정해둔다.

그렇기 때문에 DB에 저장하여 관리를 할 필요는 없다는 생각이 들었다.

 

서비스에 따라 차이는 있지만, 인증번호의 재전송이 가능한 서비스가 대부분이다.

즉, 사용자의 요청에 따라 Valid한 인증번호의 값이 언제든지 변할 수 있다.

그렇기 때문에 인증번호에 대한 빈번한 업데이트를 지원해야 하며, 빈번한 업데이트로 인한 부담/부하가 적은 편이 좋을 것이다.

 

그리고 마지막으로 어떤 저장 방식이던지 인증번호와 휴대폰 번호를 1:1로 맵핑할 수 있는 저장 방식이 필요하다.

 

이러한 조건을 충족하는 여러 방식이 있다.

간단하게는 In-memory Cache를 지원하는 모듈 (memory-cache 모듈)을 사용할 수도 있고, Redis를 사용할 수도 있을 것이다.

1. 인증번호의 생성과 전송

인증번호의 생성은 각자의 방법 또는 진행하고 있는 서비스의 비즈니스 로직에 따라 달라질 수 있는 부분인 것 같다.

인증번호를 6자리로할 것인지 4자리로 할 것인지, 또는 숫자로만 할 것인지 영문과 혼용해서 할 것인지..

가장 간단한 6자리 숫자로 인증번호를 만드는 방법은 아래의 코드와 같을 것이다.

 

가장 신경써야하는 부분은 위에서 정한 저장공간(아래 코드에서는 in-memory cache를 가정)을 핸들링하는 부분이다.

앞서 가정한 것과 같이 사용자가 인증번호를 요청한 것이 최초가 아닐 수 있기 때문에

인증번호 요청 로직을 시작할 경우, 기존의 Cache에 저장된 것을 초기화시켜주는 것이 필요하다.

값이 중복으로 저장되는 것을 방지하는 것과 함께 사용자의 재요청이 있었다면 직전 요청의 인증번호는 만료처리해야하기 때문이다.

그 후, 생성된 인증번호를 Cache에 기록하고 정책에 따라 정해진 시간만큼의 TTL을 설정하여 한시적으로만 저장할 수 있도록 한다.

 

마지막으로 해당 인증번호를 SMS 메시지로 전송하기만 하면 인증번호 생성 요청에 대한 API 구성은 충분할 것이다.

 

아래의 코드에서 작성하지는 않았지만, SMS 메시지 전송이 실패할 경우의 Error Handling에 대한 제어는 필요하다.

const postVerifyCode = (request, h) => {
	// 1. request에 대한 Validation Code...

	// 2. delete cache by phone number

	// 3. create random VerifyCode
    let verifyCode;
    for (let i = 0; i < 6; i++) {
    	verifyCode += parseInt(Math.random() * 10);
    }
    
    // 4. put (phoneNumber & verifyCode) to cache And set TTL
    
 	// 5. post sms message with external API
}

 

2. 인증번호의 검증

Cache에 저장된 휴대폰 번호(Key)와 인증번호(Value)가 정확하게 요청되었는 지를 판단하는 인증 Flow를 간단하게 표현하면 다음과 같다.

 

우선, Key 값인 휴대폰 번호로 Cache에 저장된 값이 있는지 조회해온다.

만약 조회 결과가 없다면 TTL에 따라 저장되었던 Key-Value가 삭제되었거나, 사용자가 인증번호 발급 요청없이 검증 요청을 한 경우일 것이다. 후자의 경우는 서버 레벨보다는 UI레벨에서의 제어가 더욱 필요할 것이지만, 2가지 경우 모두 다시 인증번호 발급을 요청하면 되는 것이기 때문에 적절한 에러 메시지와 함께 응답을 전달하면 된다.

 

저장된 값이 있는 경우, 사용자가 입력한 인증번호와의 일치여부를 확인하여 일치할 경우 Cache를 삭제한 뒤 이후 서비스 process를 진행하면 된다.

하지만 일치하지 않을 경우에는 악의적 공격일 수도 있지만 사용자의 오입력인 경우를 배제할 수 없기 때문에 Cache에 대한 처리 없이 적절한 인증 불일치 메시지로 응답하면 된다.

 

const confirmVerifyCode = (request, h) => {
	// 1. Validation
    // 2. Check cache by phoneNumber
    // 3. Confirm verifyCode
    // 4. Clear cache
}

Sample Code

위에서 간략하게 설명한 로직에 대한 샘플 코드는 Github 링크에서 확인할 수 있습니다.

도움이 되셨다면, Github에서 Star를 눌러주시면 감사하겠습니다 :)

https://github.com/jaestory/blog_logs/blob/master/Node.js/smsVerification.js

 

jaestory/blog_logs

Contribute to jaestory/blog_logs development by creating an account on GitHub.

github.com


위 글은 과거 Medium 블로그를 사용할 때, 작성했던 내용을 조금 변형한 것이다.

필요한 로직을 간략하게만 작성한 것이지만, 만약에 위의 내용을 실제로 API로 구현한다고 할 경우에 추가적인 수정은 불가피할 것 같다.

  • Controller Level에서 작성한 해당 로직을 Biz Level로 분리하는 작업이 필요할 것 같다.
  • Naver SENS의 적합성 여부를 고민해볼 것 같다. 가능하면 Cloud Platform을 일원화하는 것이 좋을 것 같기 때문에
  • Cache가 아닌 Redis를 사용하지 않을까 생각해본다. Single Thread인 Node Server 한 단위로 Memory-Cache를 만들게 된다면 각 요청에 대한 세션 처리가 추가로 이뤄져야 할텐데 그렇게 될 경우 인증번호 요청(발급)과 확인(검증)의 로직에 너무 큰 리소스가 사용되는 부담이 생길 것 같다.
반응형