고급 인증 기능

1. 설정

소스 코드 가져오기

이 Codelab에서는 거의 완성된 친근한 채팅 샘플 앱 버전으로 시작하므로 가장 먼저 소스 코드를 클론해야 합니다.

$ git clone https://github.com/firebase/codelab-friendlychat-web --branch security

그런 다음 security-start 디렉터리로 이동하여 이 Codelab의 나머지 부분을 작업합니다.

$ cd codelab-friendlychat-web/security-start

이제 코드를 실행할 수 있도록 종속 항목을 설치합니다. 인터넷 연결이 느린 경우 1~2분 정도 걸릴 수 있습니다.

$ npm install && (cd functions && npm install)

저장소 알아보기

security-solution/ 디렉터리에는 샘플 앱의 전체 코드가 포함되어 있습니다. security-start 디렉터리에서는 Codelab을 진행하게 되며 인증 구현의 몇 가지 중요한 부분이 누락되어 있습니다. security-start/security-solution/의 주요 파일과 기능은 다음과 같습니다.

  • functions/index.js에는 Cloud Functions 코드가 포함되어 있으며 여기에 인증 차단 함수를 작성합니다.
  • public/ - 채팅 앱의 정적 파일을 포함합니다.
  • public/scripts/main.js - 채팅 앱 JS 코드 (src/index.js)가 컴파일되는 위치
  • src/firebase-config.js: 채팅 앱을 초기화하는 데 사용되는 Firebase 구성 객체를 포함합니다.
  • src/index.js - 채팅 앱 JS 코드

Firebase CLI 가져오기

에뮬레이터 도구 모음은 Firebase CLI (명령줄 인터페이스)의 일부이며 다음 명령어를 사용하여 머신에 설치할 수 있습니다.

$ npm install -g firebase-tools@latest

webpack으로 자바스크립트를 빌드하면 public/scripts/ 디렉터리 내에 main.js가 생성됩니다.

webpack build

그런 다음 CLI가 최신 버전인지 확인합니다. 이 Codelab은 버전 11.14 이상에서 작동합니다.

$ firebase --version
11.14.2

Firebase 프로젝트에 연결

Firebase 프로젝트가 없으면 Firebase Console에서 새 Firebase 프로젝트를 만듭니다. 나중에 필요하므로 선택한 프로젝트 ID를 기록해 둡니다.

이제 이 코드를 Firebase 프로젝트에 연결해야 합니다. 먼저 다음 명령어를 실행하여 Firebase CLI에 로그인합니다.

$ firebase login

다음으로 다음 명령어를 실행하여 프로젝트 별칭을 만듭니다. $YOUR_PROJECT_ID를 Firebase 프로젝트의 ID로 바꿉니다.

$ firebase use $YOUR_PROJECT_ID

이제 앱을 실행할 준비가 되었습니다.

2. 에뮬레이터 실행

이 섹션에서는 앱을 로컬에서 실행합니다. 이제 에뮬레이터 도구 모음을 부팅할 차례입니다.

에뮬레이터 시작

Codelab 소스 디렉터리 내에서 다음 명령어를 실행하여 에뮬레이터를 시작합니다.

$ firebase emulators:start

그러면 http://127.0.0.1:5170에서 앱이 제공되고 변경 시 소스 코드가 계속 다시 빌드됩니다. 변경사항을 확인하려면 브라우저에서 로컬로 강제로 새로고침 (ctrl-shift-r)해야 합니다.

다음과 같은 출력이 표시됩니다.

i  emulators: Starting emulators: auth, functions, firestore, hosting, storage
✔  functions: Using node@16 from host.
i  firestore: Firestore Emulator logging to firestore-debug.log
✔  firestore: Firestore Emulator UI websocket is running on 9150.
i  hosting[demo-example]: Serving hosting files from: ./public
✔  hosting[demo-example]: Local server: http://127.0.0.1:5170
i  ui: Emulator UI logging to ui-debug.log
i  functions: Watching "[...]" for Cloud Functions...
✔  functions: Loaded functions definitions from source: beforecreated.
✔  functions[us-central1-beforecreated]: providers/cloud.auth/eventTypes/user.beforeCreate function initialized (http://127.0.0.1:5011/[...]/us-central1/beforecreated).
i  Running script: npm start
 
> security@1.0.0 start
> webpack --watch --progress
[...]
webpack 5.50.0 compiled with 1 warning in 990 ms

All emulators ready 메시지가 표시되면 앱을 사용할 준비가 된 것입니다.

3. MFA 구현

MFA는 이 저장소에 부분적으로 구현되었습니다. 코드를 추가하여 먼저 MFA에 사용자를 등록한 다음 MFA에 등록된 사용자에게 두 번째 단계에 대해 메시지를 표시합니다.

편집기에서 src/index.js 파일을 열고 startEnrollMultiFactor() 메서드를 찾습니다. 다음 코드를 추가하여 휴대전화 악용을 방지하는 reCAPTCHA 인증기를 설정합니다 (reCAPTCHA 인증기는 보이지 않음으로 설정되며 사용자에게 표시되지 않음).

async function startEnrollMultiFactor(phoneNumber) {
  const recaptchaVerifier = new RecaptchaVerifier(
    "recaptcha",
    { size: "invisible" },
    getAuth()
  );

그런 다음 finishEnrollMultiFactor() 메서드를 찾아 다음을 추가하여 두 번째 단계를 등록합니다.

// Completes MFA enrollment once a verification code is obtained.
async function finishEnrollMultiFactor(verificationCode) {
  // Ask user for the verification code. Then:
  const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
  const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
 
  // Complete enrollment.
  await multiFactor(getAuth().currentUser)
    .enroll(multiFactorAssertion)
    .catch(function (error) {
      alert(`Error finishing second factor enrollment. ${error}`);
      throw error;
    });
  verificationId = null;
}

다음으로, signIn 함수를 찾아 다중 인증(MFA)에 등록된 사용자에게 두 번째 단계를 입력하도록 요청하는 다음 제어 흐름을 추가합니다.

async function signIn() {
  // Sign in Firebase using popup auth and Google as the identity provider.
  var provider = new GoogleAuthProvider();
  await signInWithPopup(getAuth(), provider)
    .then(function (userCredential) {
      // User successfully signed in and is not enrolled with a second factor.
    })
    .catch(function (error) {
      if (error.code == "auth/multi-factor-auth-required") {
        multiFactorResolver = getMultiFactorResolver(getAuth(), error);
        displaySecondFactor(multiFactorResolver.hints);
      } else {
        alert(`Error signing in user. ${error}`);
      }
    });
}

여기서 호출된 함수를 비롯한 나머지 구현은 이미 완료되었습니다. 작동 방식을 확인하려면 파일의 나머지 부분을 탐색하세요.

4. 에뮬레이터에서 MFA로 로그인 시도

이제 MFA 구현을 시도해 보세요. 에뮬레이터가 여전히 실행 중인지 확인하고 localhost:5170에서 로컬 호스팅 앱을 방문합니다. 로그인해 보세요. MFA 코드를 입력하라는 메시지가 표시되면 터미널 창에 MFA 코드가 표시됩니다.

에뮬레이터는 다중 인증(MFA)을 완벽하게 지원하므로 개발 환경을 완전히 독립적으로 포함할 수 있습니다.

MFA 구현에 대한 자세한 내용은 참조 문서를 확인하세요.

5. 차단 함수 만들기

일부 애플리케이션은 특정 사용자 그룹만 사용하도록 제작되었습니다. 이러한 경우에 사용자가 앱에 가입하거나 로그인하는 데 필요한 맞춤 요구사항을 만들 수 있습니다.

차단 함수가 제공하는 것은 바로 커스텀 인증 요구사항을 만드는 방법입니다. Cloud Functions이지만 대부분의 함수와 달리 사용자가 가입하거나 로그인하려고 할 때 동기식으로 실행됩니다.

차단 함수를 만들려면 편집기에서 functions/index.js을 열고 주석 처리된 beforecreated 함수를 찾습니다.

example.com 도메인을 가진 사용자만 계정을 만들 수 있도록 허용하는 다음 코드로 교체합니다.

exports.beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  // Only users of a specific domain can sign up.
  if (!user.email || !user.email.endsWith("@example.com")) {
    throw new HttpsError("invalid-argument", "Unauthorized email");
  }
});

6. 에뮬레이터에서 차단 함수 사용해 보기

차단 함수를 사용해 보려면 에뮬레이터가 실행 중인지 확인하고 웹 앱의 localhost:5170에서 로그아웃하세요.

그런 다음 example.com(으)로 끝나지 않는 이메일 주소로 계정을 만들어 보세요. 차단 함수는 작업이 성공하지 못하게 합니다.

example.com(으)로 끝나는 이메일 주소로 다시 시도해 보세요. 계정이 생성됩니다.

차단 함수를 사용하면 인증에 필요한 모든 제한을 만들 수 있습니다. 자세한 내용은 참조 문서를 확인하세요.

요약

수고하셨습니다. 사용자의 계정을 안전하게 보호할 수 있도록 웹 앱에 다중 인증(MFA)을 추가한 다음, 차단 함수를 사용하여 사용자가 가입할 수 있는 맞춤 요구사항을 만들었습니다. 확실히 GIF를 얻으셨습니다.

사무실에서 루프톱 댄스를 추는 사람들의 gif입니다.