高度な認証機能

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 を取得する

Emulator Suite は 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 コンソールで新しい Firebase プロジェクトを作成します。後で必要になるため、選択したプロジェクト ID をメモしておきます。

次に、このコードを Firebase プロジェクトに接続する必要があります。まず、次のコマンドを実行して Firebase CLI にログインします。

$ firebase login

次に、次のコマンドを実行してプロジェクト エイリアスを作成します。$YOUR_PROJECT_ID は、Firebase プロジェクトの ID に置き換えます。

$ firebase use $YOUR_PROJECT_ID

これで、アプリを実行する準備が整いました。

2. エミュレータを実行する

このセクションでは、アプリをローカルで実行します。いよいよ Emulator Suite を起動します。

エミュレータを起動する

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 に登録したユーザーに 2 つ目の要素を求めるプロンプトを表示します。

エディタで src/index.js ファイルを開き、startEnrollMultiFactor() メソッドを見つけます。次のコードを追加して、携帯電話の不正使用を防ぐための reCAPTCHA ベリファイアを設定します(reCAPTCHA ベリファイアは非表示に設定されており、ユーザーには表示されません)。

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

次に、finishEnrollMultiFactor() メソッドを探し、以下を追加して 2 つ目の要素を登録します。

// 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 の実装の詳細については、リファレンス ドキュメントをご覧ください。

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 のメールアドレスを使ってもう一度お試しください。アカウントが作成されます。

ブロッキング関数を使用すると、認証に関して必要な制限を作成できます。詳しくは、リファレンス ドキュメントをご覧ください。

内容のまとめ

お疲れさまでした。ユーザーがアカウントを安全に保つために多要素認証をウェブアプリに追加し、ブロッキング機能を使用してユーザーが登録するためのカスタム要件を作成しました。GIF を獲得しました!

オフィスで屋根を上げて踊る人々の GIF