자바스크립트를 사용하여 전화번호로 Firebase에 인증하기

Firebase 인증을 사용하면 사용자의 전화로 SMS 메시지를 전송하여 로그인하는 것이 가능합니다. 사용자는 SMS 메시지에 포함된 일회용 코드를 사용하여 로그인합니다.

앱에 전화번호 로그인을 추가하는 가장 쉬운 방법은 FirebaseUI를 사용하는 것입니다. 여기에는 비밀번호 기반 및 제휴 로그인뿐만 아니라 전화번호 로그인의 로그인 과정을 구현하는 삽입형 로그인 위젯이 포함됩니다. 이 문서에서는 Firebase SDK를 사용하여 전화번호 로그인 과정을 구현하는 방법을 설명합니다.

시작하기 전에

아직 복사하지 않았다면 자바스크립트 프로젝트에 Firebase 추가의 설명대로 Firebase Console의 초기화 스니펫을 프로젝트에 복사합니다.

보안 문제

전화번호만 사용하는 인증은 편리하기는 하지만, 전화번호 소유권은 사용자 간에 쉽게 이전될 수 있으므로 다른 방식보다 보안성이 떨어집니다. 또한 기기에 여러 사용자 프로필이 있는 경우 SMS 메시지를 받을 수 있는 사람이라면 누구든지 기기의 전화번호로 계정에 로그인할 수 있습니다.

앱에서 전화번호 기반 로그인을 사용하는 경우 더 안전한 로그인 방법을 함께 제공해야 하고, 전화번호 로그인을 사용하면 보안이 약화된다는 점을 사용자에게 알려야 합니다.

Firebase 프로젝트에서 전화번호 로그인 사용 설정

SMS 메시지로 사용자를 로그인 처리하려면 우선 Firebase 프로젝트에서 전화번호 로그인 방법을 사용 설정해야 합니다.

  1. Firebase Console에서 인증 섹션을 엽니다.
  2. 로그인 방법 페이지에서 전화번호 로그인 방법을 사용 설정합니다.
  3. 같은 페이지에서, OAuth 리디렉션 도메인 섹션에 앱을 호스팅할 도메인이 나열되지 않은 경우 도메인을 추가합니다.

Firebase의 전화번호 로그인 요청 할당량은 대부분의 앱에서 불편을 겪지 않을 정도로 충분히 높습니다. 그러나 전화번호 인증으로 처리할 로그인 사용자가 매우 많다면 요금제를 업그레이드해야 할 수도 있습니다. 자세한 내용은 가격 책정 페이지를 참조하세요.

reCAPTCHA 인증기 설정

전화번호로 사용자를 로그인 처리하려면 우선 Firebase의 reCAPTCHA 인증기를 설정해야 합니다. Firebase는 전화번호 인증 요청이 앱의 허용 도메인 중 하나에서 전송되었는지 확인하는 등 reCAPTCHA를 통해 악용을 방지합니다.

reCAPTCHA 클라이언트를 직접 설정할 필요는 없습니다. Firebase SDK의 RecaptchaVerifier 객체를 사용하면 Firebase에서 필요한 클라이언트 키와 보안 비밀을 자동으로 만들고 처리합니다.

RecaptchaVerifier 객체는 보통 사용자 작업 없이 사용자를 인증할 수 있는 보이지 않는 reCAPTCHA뿐만 아니라 항상 사용자가 개입해야 통과할 수 있는 reCAPTCHA 위젯도 지원합니다.

기본 렌더링된 reCAPTCHA는 reCAPTCHA를 렌더링하기 전에 Auth 인스턴스에서 언어 코드를 업데이트하여 사용자 환경설정에 맞게 현지화될 수 있습니다. 앞서 설명한 현지화는 사용자에게 보낸 인증 코드가 포함된 SMS 메시지에도 적용됩니다.

웹 모듈식 API

import { getAuth } from "firebase/auth";

const auth = getAuth();
auth.languageCode = 'it';
// To apply the default browser preference instead of explicitly setting it.
// auth.useDeviceLanguage();

웹 네임스페이스화된 API

firebase.auth().languageCode = 'it';
// To apply the default browser preference instead of explicitly setting it.
// firebase.auth().useDeviceLanguage();

보이지 않는 reCAPTCHA 사용

보이지 않는 reCAPTCHA를 사용하려면 size 매개변수를 invisible로 설정하고 로그인 양식을 제출하는 버튼의 ID를 지정하여 RecaptchaVerifier 객체를 만듭니다. 예를 들면 다음과 같습니다.

웹 모듈식 API

import { getAuth, RecaptchaVerifier } from "firebase/auth";

const auth = getAuth();
window.recaptchaVerifier = new RecaptchaVerifier(auth, 'sign-in-button', {
  'size': 'invisible',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    onSignInSubmit();
  }
});

웹 네임스페이스화된 API

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
  'size': 'invisible',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    onSignInSubmit();
  }
});

reCAPTCHA 위젯 사용

보이는 reCAPTCHA 위젯을 사용하려면 페이지에 위젯을 포함할 요소를 만들고 RecaptchaVerifier 객체를 만들면서 컨테이너의 ID를 지정합니다. 예를 들면 다음과 같습니다.

웹 모듈식 API

import { getAuth, RecaptchaVerifier } from "firebase/auth";

const auth = getAuth();
window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {});

웹 네임스페이스화된 API

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');

선택사항: reCAPTCHA 매개변수 지정

필요에 따라 RecaptchaVerifier 객체에 사용자가 reCAPTCHA를 완료할 때 또는 사용자가 양식을 제출하기 전에 reCAPTCHA가 만료될 때 호출되는 콜백 함수를 설정할 수 있습니다.

웹 모듈식 API

import { getAuth, RecaptchaVerifier } from "firebase/auth";

const auth = getAuth();
window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
  'size': 'normal',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    // ...
  },
  'expired-callback': () => {
    // Response expired. Ask user to solve reCAPTCHA again.
    // ...
  }
});

웹 네임스페이스화된 API

window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
  'size': 'normal',
  'callback': (response) => {
    // reCAPTCHA solved, allow signInWithPhoneNumber.
    // ...
  },
  'expired-callback': () => {
    // Response expired. Ask user to solve reCAPTCHA again.
    // ...
  }
});

선택사항: reCAPTCHA 사전 렌더링

로그인 요청을 제출하기 전에 reCAPTCHA를 사전 렌더링하려면 render를 호출합니다.

웹 모듈식 API

recaptchaVerifier.render().then((widgetId) => {
  window.recaptchaWidgetId = widgetId;
});

웹 네임스페이스화된 API

recaptchaVerifier.render().then((widgetId) => {
  window.recaptchaWidgetId = widgetId;
});

render가 확인되면 reCAPTCHA의 위젯 ID를 가져옵니다. 이 ID를 사용하여 reCAPTCHA API를 호출할 수 있습니다.

웹 모듈식 API

const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);

웹 네임스페이스화된 API

const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId);

사용자 휴대전화로 인증 코드 전송

전화번호 로그인을 시작하려면 사용자에게 전화번호를 제공하도록 요청하는 인터페이스를 표시하고 signInWithPhoneNumber를 호출하여 Firebase에서 사용자의 휴대전화에 SMS 메시지로 인증 코드를 전송하도록 요청합니다.

  1. 사용자의 전화번호를 확인합니다.

    지역에 따라 현지 법규가 다르지만, 일반적으로는 사용자가 제반 상황을 미리 알 수 있도록 휴대전화 로그인을 사용하면 인증용 SMS 메시지가 발송되고 일반 요금이 부과될 수 있다는 점을 알려야 합니다.

  2. signInWithPhoneNumber를 호출하면서 사용자의 전화번호와 이전에 만든 RecaptchaVerifier를 전달합니다.

    웹 모듈식 API

    import { getAuth, signInWithPhoneNumber } from "firebase/auth";
    
    const phoneNumber = getPhoneNumberFromUserInput();
    const appVerifier = window.recaptchaVerifier;
    
    const auth = getAuth();
    signInWithPhoneNumber(auth, phoneNumber, appVerifier)
        .then((confirmationResult) => {
          // SMS sent. Prompt user to type the code from the message, then sign the
          // user in with confirmationResult.confirm(code).
          window.confirmationResult = confirmationResult;
          // ...
        }).catch((error) => {
          // Error; SMS not sent
          // ...
        });

    웹 네임스페이스화된 API

    const phoneNumber = getPhoneNumberFromUserInput();
    const appVerifier = window.recaptchaVerifier;
    firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
        .then((confirmationResult) => {
          // SMS sent. Prompt user to type the code from the message, then sign the
          // user in with confirmationResult.confirm(code).
          window.confirmationResult = confirmationResult;
          // ...
        }).catch((error) => {
          // Error; SMS not sent
          // ...
        });
    signInWithPhoneNumber에서 오류가 발생하면 사용자가 다시 시도할 수 있도록 reCAPTCHA를 재설정합니다.
    grecaptcha.reset(window.recaptchaWidgetId);
    
    // Or, if you haven't stored the widget ID:
    window.recaptchaVerifier.render().then(function(widgetId) {
      grecaptcha.reset(widgetId);
    });
    

signInWithPhoneNumber 메서드는 사용자에게 reCAPTCHA 테스트를 제시하고, 사용자가 테스트를 통과하면 Firebase 인증에서 사용자의 휴대전화로 인증 코드가 포함된 SMS 메시지를 전송하도록 요청합니다.

인증 코드로 사용자 로그인 처리

signInWithPhoneNumber 호출이 성공하면 사용자에게 SMS 메시지로 받은 인증 코드를 입력하라는 메시지를 표시합니다. 그런 다음 signInWithPhoneNumber의 처리 핸들러(즉, then 블록)에 전달된 ConfirmationResult 객체의 confirm 메서드에 코드를 전달하여 사용자를 로그인 처리합니다. 예를 들면 다음과 같습니다.

웹 모듈식 API

const code = getCodeFromUserInput();
confirmationResult.confirm(code).then((result) => {
  // User signed in successfully.
  const user = result.user;
  // ...
}).catch((error) => {
  // User couldn't sign in (bad verification code?)
  // ...
});

웹 네임스페이스화된 API

const code = getCodeFromUserInput();
confirmationResult.confirm(code).then((result) => {
  // User signed in successfully.
  const user = result.user;
  // ...
}).catch((error) => {
  // User couldn't sign in (bad verification code?)
  // ...
});

confirm 호출이 성공하면 사용자가 로그인된 것입니다.

중간 AuthCredential 객체 가져오기

사용자 계정의 AuthCredential 객체를 가져와야 하는 경우 confirm을 호출하는 대신 확인 결과의 인증 코드 및 인증 코드를 PhoneAuthProvider.credential에 전달합니다.

var credential = firebase.auth.PhoneAuthProvider.credential(confirmationResult.verificationId, code);

그런 다음 인증 정보를 사용하여 사용자를 로그인 처리할 수 있습니다.

firebase.auth().signInWithCredential(credential);

가상 전화번호로 테스트

Firebase Console을 통해 개발용 가상 전화번호를 설정할 수 있습니다. 가상 전화번호로 테스트하면 다음과 같은 이점이 있습니다.

  • 사용 할당량을 소비하지 않고 전화번호 인증을 테스트할 수 있습니다.
  • 실제 SMS 메시지를 보내지 않고 전화번호 인증을 테스트할 수 있습니다.
  • 제한 없이 동일한 전화번호로 연속 테스트를 실행할 수 있습니다. 앱 스토어 검토 절차 중에 검토자가 동일한 전화번호를 테스트에 사용하게 될 경우 번호가 거부될 위험이 최소화됩니다.
  • 별도의 노력 없이도 개발 환경에서 쉽게 테스트할 수 있습니다. 예를 들어 Google Play 서비스 없이 iOS 시뮬레이터 또는 Android Emulator에서 개발할 수 있습니다.
  • 프로덕션 환경에서 실제 전화번호에 일반적으로 적용되는 보안 검사에 의해 차단되지 않고 통합 테스트를 작성할 수 있습니다.

가상 전화번호는 다음 요구사항을 충족해야 합니다.

  1. 실제로 존재하지 않는 가상의 전화번호여야 합니다. Firebase 인증에서는 실제 사용자가 사용하는 기존 전화번호를 테스트 전화번호로 설정할 수 없습니다. 전화번호를 만드는 방법 중 하나는 555로 시작하는 번호(예: +1 650-555-3434)를 미국 테스트 전화번호로 사용하는 것입니다.
  2. 길이 및 기타 조건에 맞게 전화번호 형식을 올바르게 지정해야 합니다. 테스트 전화번호도 실제 사용자의 전화번호와 동일한 유효성 검사를 거칩니다.
  3. 개발용으로 최대 10개의 전화번호를 추가할 수 있습니다.
  4. 다른 사람이 추측하기 어려운 테스트 전화번호와 코드를 사용하고 자주 변경합니다.

가상 전화번호 및 인증 코드 생성

  1. Firebase Console에서 인증 섹션을 엽니다.
  2. 전화 제공업체를 아직 사용 설정하지 않은 경우 로그인 방법 탭에서 사용 설정합니다.
  3. 테스트용 전화번호 아코디언 메뉴를 엽니다.
  4. 테스트할 전화번호(예: +1 650-555-3434)를 입력합니다.
  5. 해당 번호에 사용할 6자리 인증 코드(예: 654321)를 입력합니다.
  6. 번호를 추가합니다. 필요한 경우 해당 행 위에 마우스를 가져가서 휴지통 아이콘을 클릭하면 전화번호와 코드를 삭제할 수 있습니다.

직접 테스트

애플리케이션에서 직접 가상 전화번호를 사용할 수 있습니다. 따라서 할당량 문제나 제한 없이 개발 단계 중에 직접 테스트를 수행할 수 있습니다. 또한 Google Play 서비스를 설치하지 않고 iOS 시뮬레이터 또는 Android 에뮬레이터에서 바로 테스트할 수 있습니다.

가상 전화번호를 입력하고 인증 코드를 보내도 실제 SMS 메시지가 전송되지 않습니다. 그 대신 이전에 구성한 인증 코드를 입력하여 로그인을 완료해야 합니다.

로그인이 완료되면 해당 전화번호로 Firebase 사용자가 생성됩니다. 사용자는 실제 전화번호 사용자와 동일한 행동과 속성을 가지며 동일한 방식으로 실시간 데이터베이스, Cloud Firestore 및 기타 서비스에 액세스할 수 있습니다. 이 절차 중에 발급된 ID 토큰에는 실제 전화번호 사용자와 같은 서명이 있습니다.

액세스 권한을 좀 더 제한하고 싶은 경우에 사용할 수 있는 다른 방법은 이러한 사용자에게 커스텀 클레임을 통해 테스트 역할을 설정하여 허위 사용자로 구분하는 것입니다.

통합 테스트

Firebase 인증은 직접 테스트 외에도 전화 인증 테스트용 통합 테스트를 작성할 수 있는 API를 제공합니다. 이러한 API는 웹에서 reCAPTCHA 요구사항을, iOS에서 백그라운드 푸시 알림을 중지하여 앱 인증을 사용 중지합니다. 따라서 이러한 흐름에서 테스트를 자동화하고 더 쉽게 구현할 수 있습니다. 또한 Android에서 즉시 인증 흐름을 테스트하는 기능도 제공합니다.

웹에서 firebase.auth.RecaptchaVerifier를 렌더링하기 전에 appVerificationDisabledForTestingtrue로 설정합니다. reCAPTCHA가 자동으로 확인되어 직접 처리하지 않고도 전화번호를 전달할 수 있습니다. reCAPTCHA를 사용 중지하더라도 실제 전화번호를 사용하면 로그인에 실패하게 됩니다. 이 API로는 가상 전화번호만 사용할 수 있습니다.

// Turn off phone auth app verification.
firebase.auth().settings.appVerificationDisabledForTesting = true;

var phoneNumber = "+16505554567";
var testVerificationCode = "123456";

// This will render a fake reCAPTCHA as appVerificationDisabledForTesting is true.
// This will resolve after rendering without app verification.
var appVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');
// signInWithPhoneNumber will call appVerifier.verify() which will resolve with a fake
// reCAPTCHA response.
firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
    .then(function (confirmationResult) {
      // confirmationResult can resolve with the fictional testVerificationCode above.
      return confirmationResult.confirm(testVerificationCode)
    }).catch(function (error) {
      // Error; SMS not sent
      // ...
    });

앱 인증을 사용 중지하면 보이거나 보이지 않는 모의 reCAPTCHA 앱 인증기가 다르게 작동합니다.

  • 보이는 reCAPTCHA: 보이는 reCAPTCHA가 appVerifier.render()를 통해 렌더링되면 1초 내에 자동으로 자체 확인됩니다. 렌더링 직후 사용자가 reCAPTCHA를 클릭하는 것과 다를 바가 없습니다. 일정 시간이 지나면 reCAPTCHA 응답이 만료되며 이후 다시 자동으로 확인 처리됩니다.
  • 보이지 않는 reCAPTCHA: 보이지 않는 reCAPTCHA는 렌더링 시 자동으로 확인되지 않으며, 그 대신 appVerifier.verify()를 호출하거나 reCAPTCHA의 버튼 앵커를 클릭하면 1초 내에 확인됩니다. 마찬가지로 일정 시간이 지나면 응답이 만료되며 appVerifier.verify() 호출을 사용하거나 reCAPTCHA의 버튼 앵커를 다시 클릭해야만 자동으로 확인 처리됩니다.

모의 reCAPTCHA가 확인되면 해당하는 콜백 함수가 가짜 응답과 함께 정상적으로 트리거됩니다. 만료 콜백도 지정한 경우 만료 시에 트리거됩니다.

다음 단계

사용자가 처음으로 로그인하면 신규 사용자 계정이 생성되고 사용자가 로그인할 때 사용한 사용자 인증 정보(사용자 이름과 비밀번호, 전화번호 또는 인증 제공업체 정보)에 연결됩니다. 이 신규 계정은 Firebase 프로젝트에 저장되며 사용자의 로그인 방법과 무관하게 프로젝트 내의 모든 앱에서 사용자 본인 확인에 사용할 수 있습니다.

  • 앱에서 사용자의 인증 상태를 파악할 때 권장하는 방법은 Auth 객체에 관찰자를 설정하는 것입니다. 그러면 User 객체로부터 사용자의 기본 프로필 정보를 가져올 수 있습니다. 사용자 관리를 참조하세요.

  • Firebase 실시간 데이터베이스와 Cloud Storage 보안 규칙auth 변수에서 로그인한 사용자의 고유 사용자 ID를 가져온 후 이 ID를 통해 사용자가 액세스할 수 있는 데이터를 관리할 수 있습니다.

인증 제공업체의 사용자 인증 정보를 기존 사용자 계정에 연결하면 사용자가 여러 인증 제공업체를 통해 앱에 로그인할 수 있습니다.

사용자를 로그아웃시키려면 signOut을 호출합니다.

웹 모듈식 API

import { getAuth, signOut } from "firebase/auth";

const auth = getAuth();
signOut(auth).then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});

웹 네임스페이스화된 API

firebase.auth().signOut().then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});