[TIL] - React에서 SMS로 로그인하기

장광진·2024년 6월 24일

CS

목록 보기
8/13

소개팅 웹 앱을 개발하던 도중 로그인 처리를 어떤 방식으로 해야할지 고민이었다. 다른 소개팅 앱의 레퍼런스를 확인하던 중 대부분의 서비스들이 핸드폰 번호를 사용한 SMS로그인 형식을 사용하고 있었다.
사용자가 번호를 입력하면 인증번호를 보내주어 이를 PW처럼 사용하는 방식이었는데, 이 방식을 사용하면 미세하게나마 우리가 돈을 내야했다 ㅠㅠㅠ... 그래서 어떻게 돈을 안들이고 로그인을 처리할 수 있을까 고민하던 중 역으로 사용자가 우리한테 메시지를 보내면 어떨까? 라는 생각이 들어 이를 적용하기로 하였다.

프로세스는 크게 다음과 같다.

1. 사용자가 핸드폰 번호를 입력한 후 '인증 버튼'을 누른다.

2. 핸드폰 번호가 DB에 있는지 확인하고 있으면 로그인, 없으면 회원가입으로 넘어간다.

3. 모바일에서 SMS로 바로 이동한 후 서버에서 보내준 랜덤한 verificationCode를 body에 담아 우리 측 이메일 주소로 바로 보낼 수 있도록 설정한다.

4. 사용자가 verificationCode를 담은 sms를 보내면 서버에서 맞는지 확인하여 회원가입 또는 로그인을 승인시킨다.


1. 사용자가 핸드폰 번호를 입력한 후 '인증 버튼'을 누른다.


위처럼 본인의 핸드폰 번호를 입력하고 버튼을 누르게 되면 임의의 verificationCode를 생성하는 API와 회원가입 유/무를 확인하는 API를 호출한다. 그 후 바로 sms로 이동한다.

const onSubmit = async (data: z.infer<typeof FormSchema>) => {
    try {
      const authenticationCode = await getAuthenticationCode(data.pin);

      if (authenticationCode.data.code) {
        setUserData('phoneNumber', data.pin);
        setUserData('verificationCode', authenticationCode.data.code);
		...

2. 핸드폰 번호가 DB에 있는지 확인하고 있으면 로그인, 없으면 회원가입으로 넘어간다.

onSubmit 함수에 회원가입 유/무를 판별하는 API를 함께 호출한다.

if (authenticationCode.data.code) {
        setUserData('phoneNumber', data.pin);
        setUserData('verificationCode', authenticationCode.data.code);

        const memberStatus = await getExistMember(data.pin);
        setUserExist(memberStatus.data.exists);

phoneNumber와 verificationCode는 추후에 로그인 및 회원가입시 필요하기에 데이터를 전역으로 관리한다.

3. 모바일에서 SMS로 바로 이동한 후 서버에서 보내준 랜덤한 verificationCode를 body에 담아 우리 측 이메일 주소로 바로 보낼 수 있도록 설정한다.


버튼을 누르면 SMS로 이동하여 자동으로 메시지 내용에 서버에서 발급한 vericifationCode를 넣어놓고 email도 서버에서 관리하는 email로 자동으로 설정한다. 유저는 메시지를 보내기만 하면 되므로 불편한 점이 사라지고 우리도 메시지를 보낼 필요가 없어지기 떄문에 win-win작전이라고 볼 수 있다 ^^

// 핸드폰 기종에 따라 분기
        const userAgent = navigator.userAgent.toLowerCase();
        let bodyPrefix = '?body=';

        if (
          userAgent.indexOf('iphone') > -1 ||
          userAgent.indexOf('ipad') > -1 ||
          userAgent.indexOf('ipod') > -1
        ) {
          bodyPrefix = '&body=';
        }

        const smsUrl = `sms:${import.meta.env.VITE_DUETT_EMAIL}${bodyPrefix}${encodeURIComponent(authenticationCode?.data?.code)}`;
        window.location.href = smsUrl;

이때 안드로이드와 IOS는 각각 sms로 연결하는 방식이 다르다. 안드는 ?body로 IOS는 &body로 붙여줘야 sms로 바로 넘어가는 현상을 확인할 수 있다. sms로 이동할때 sms: 뒤에 이메일을 붙여주면 바로 그 메일로 보낼 수 있는 환경을 제공하고 bodyPrefix 뒤에 내용물은 바로 sms body에 넣어준다. 이와 같은 세팅으로 우리는 버튼 하나로 코드를 보낼 수 있게 되었다! 이제 백엔드에서 이 코드와 phoneNumber의 유저의 코드가 일치하는지 확인한 후 요청을 승인해준다.

const onSubmit = async (data: z.infer<typeof FormSchema>) => {
    try {
      const authenticationCode = await getAuthenticationCode(data.pin);

      if (authenticationCode.data.code) {
        setUserData('phoneNumber', data.pin);
        setUserData('verificationCode', authenticationCode.data.code);

        const memberStatus = await getExistMember(data.pin);
        setUserExist(memberStatus.data.exists);

        // 핸드폰 기종에 따라 분기
        const userAgent = navigator.userAgent.toLowerCase();
        let bodyPrefix = '?body=';

        if (
          userAgent.indexOf('iphone') > -1 ||
          userAgent.indexOf('ipad') > -1 ||
          userAgent.indexOf('ipod') > -1
        ) {
          bodyPrefix = '&body=';
        }

        const smsUrl = `sms:${import.meta.env.VITE_DUETT_EMAIL}${bodyPrefix}${encodeURIComponent(authenticationCode?.data?.code)}`;
        window.location.href = smsUrl;

        toast({
          title: '인증 메시지가 전송되었습니다.',
        });

        setIsSubmitted(true);
      } else {
        throw new Error('reponse가 없나?');
      }
    } catch (error) {
      console.error(error);
      toast({
        title: `인증 에러: ${error}`,
        description: '인증 메시지 전송에 실패했습니다. 다시 시도해주세요.',
      });
    }
  };
profile
점진적 과부하

0개의 댓글