고급 인증 기능

1. 설정

소스 코드 가져오기

이 Codelab에서는 거의 완성된 버전의 Friendly Chat 샘플 앱으로 시작합니다. 따라서 먼저 소스 코드를 클론해야 합니다.

$ 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으로 JavaScript를 빌드하면 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

모든 에뮬레이터가 준비됨 메시지가 표시되면 앱을 사용할 수 있습니다.

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에 등록된 사용자에게 2단계 인증을 입력하라는 메시지를 표시하는 다음 제어 흐름을 추가합니다.

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