マルチテナンシーを使用した認証

このドキュメントでは、マルチテナント Identity Platform 環境でユーザーを認証する方法を説明します。

始める前に

プロジェクトでマルチテナンシーを有効にして、テナントを構成していることを確認してください。その方法については、マルチテナンシーのスタートガイドをご覧ください。

また、Client SDK をアプリに追加する必要もあります。

  1. Cloud Console で Identity Platform ページに移動します。
    Identity Platform ユーザーページに移動

  2. 右上の [アプリケーション設定の詳細] をクリックします。

  3. コードをウェブアプリにコピーします。たとえば、次のようなものです。

    <script src="https://www.gstatic.com/firebasejs/x.x.x/firebase.js"></script>
    <script>
    // Initialize Identity Platform
    const config = {
      apiKey: "...",
      authDomain: "my-app-12345.firebaseapp.com"
    };
    firebase.initializeApp(config);
    </script>
    

テナントでのログイン

テナントにログインするには、テナント ID を auth オブジェクトに渡す必要があります。tenantId はページの再読み込み時に保持されないことに注意してください。

var tenantId = TENANT_ID1;
firebase.auth().tenantId = tenantId;

テナント ID を変更またはリセットするまで、この auth インスタンスからのすべてのログイン リクエストには、テナント ID(上記の例では TENANT_ID1)が含まれます。

単一または複数の auth インスタンスを使用して、複数のテナントを操作できます。

1 つの auth インスタンスを使用するには、テナント間の切り替え時にはいつでも tenantId プロパティを変更します。プロジェクト レベルの IdP に戻るのを元に戻すには、tenantIdnull に設定します。

// One Auth instance
// Switch to tenant1
firebase.auth().tenantId = 'tenant1';
// Switch to tenant2
firebase.auth().tenantId = 'tenant2';
// Switch back to project level IdPs
firebase.auth().tenantId = null;

複数のインスタンスを使用するには、テナントごとに新しい auth インスタンスを作成し、異なる ID にインスタンスを割り当てます。

// Multiple Auth instances
firebase.initializeApp(config, 'app1_for_tenantId1');
firebase.initializeApp(config, 'app2_for_tenantId2');

const auth1 = firebase.app('app1').auth();
const auth2 = firebase.app('app2').auth();

auth1.tenantId = 'tenant1';
auth2.tenantId = 'tenant2';

テナントでログインすると、テナント ユーザーはそのテナントに設定された user.tenantId で返されます。auth インスタンスで tenantId を後で切り替えても、currentUser プロパティは変更されず、前のテナントと同じユーザーを指すことに注意してください。

// Switch to TENANT_ID1
firebase.auth().tenantId = 'TENANT_ID1';

// Sign in with tenant
firebase.auth().signInWithEmailAndPassword(email, password)
  .then((result) => {
    const user = result.user;
    // user.tenantId is set to 'TENANT_ID1'.
    // Switch to 'TENANT_ID2'.
    firebase.auth().tenantId = 'TENANT_ID2';
    // firebase.auth().currentUser still point to the user.
    // firebase.auth().currentUser.tenantId is 'TENANT_ID1'.
  });

// You could also get the current user from Auth state observer.
firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    // User is signed in.
    // user.tenantId is set to 'TENANT_ID1'.
  } else {
    // No user is signed in.
  }
});

メール / パスワードのアカウント

次の例は、新しいユーザーを登録する方法を示しています。

firebase.auth().tenantId = 'TENANT_ID';

firebase.auth().createUserWithEmailAndPassword(email, password)
  .then((result) => {
    // result.user.tenantId is 'TENANT_ID'.
  }).catch((error) => {
    // Handle error.
  });

既存のユーザーにログインするには:

firebase.auth().tenantId = 'TENANT_ID';

firebase.auth().signInWithEmailAndPassword(email, password)
  .then((result) => {
    // result.user.tenantId is 'TENANT_ID'.
  }).catch((error) => {
    // Handle error.
  });

SAML

SAML プロバイダでログインするには、Cloud Console のプロバイダ ID を使用して SAMLAuthProvider インスタンスをインスタンス化します。

const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');

次に、ポップアップまたはリダイレクト フローを使用して、SAML プロバイダにログインできます。

次の例は、ポップアップ フローを示しています。

// Switch to TENANT_ID1.
firebase.auth().tenantId = 'TENANT_ID1';

//Sign-in with popup.
firebase.auth().signInWithPopup(provider)
  .then((result) => {
    // User is signed in.
    // tenant ID is available in result.user.tenantId.
    // Identity provider data is available in result.additionalUserInfo.profile.
  })
  .catch((error) => {
    // Handle error.
  });

リダイレクト

次の例は、リダイレクト フローを示しています。

// Switch to TENANT_ID1.
firebase.auth().tenantId = 'TENANT_ID1';

// Sign-in with redirect.
firebase.auth().signInWithRedirect(provider);

// After the user completes sign-in and returns to the app, you can get
// the sign-in result by calling getRedirectResult. However, if they sign out
// and sign in again with an IdP, no tenant is used.
firebase.auth().getRedirectResult()
  .then((result) => {
    // User is signed in.
    // The tenant ID available in result.user.tenantId.
    // Identity provider data is available in result.additionalUserInfo.profile.
  })
  .catch((error) => {
    // Handle error.
  });

どちらの場合も、auth インスタンスに正しいテナント ID を設定してください。

認証フローを開始するには、ユーザーにメールアドレスの入力を求めるインターフェースを表示し、sendSignInLinkToEmail を呼び出して認証リンクをユーザーに送信します。メールを送信する前に、auth インスタンスに正しいテナント ID を設定してください。

// Switch to TENANT_ID1
firebase.auth().tenantId = 'TENANT_ID1';

firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
  .then(function() {
    // The link was successfully sent. Inform the user.
    // Save the email locally so you don't need to ask the user for it again
    // if they open the link on the same device.
    window.localStorage.setItem('emailForSignIn', email);
  })
  .catch(function(error) {
    // Some error occurred, you can inspect the code: error.code
  });

ランディング ページでログインを完了するには、まずメールリンクのテナント ID を解析し、auth インスタンスに設定します。次に、ユーザーのメールアドレスとワンタイム コードを含む実際のメールリンクを指定して、signInWithEmailLink を呼び出します。

if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
  const actionCodeUrl = firebase.auth.ActionCodeURL.parseLink(window.location.href);
  if (actionCodeUrl.tenantId) {
      firebase.auth().tenantId = actionCodeUrl.tenantId;
  }
  let email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  firebase.auth().signInWithEmailLink(email, window.location.href)
    .then((result) => {
      // User is signed in.
      // tenant ID available in result.user.tenantId.
    });
}

カスタム トークンの作成

マルチテナント対応カスタム トークンの作成は、通常のカスタム トークンの作成と同じです。auth インスタンスで正しいテナント ID が設定されている限り、最上位の tenant_id クレームが結果の JWT に追加されます。カスタム トークンの作成方法と使用方法の詳細については、カスタム トークンの作成をご覧ください。

次の例は、Admin SDK を使用してカスタム トークンを作成する方法を示しています。

// Ensure you're using a tenant-aware auth instance
const tenantManager = admin.auth().tenantManager();
const tenantAuth = tenantManager.authForTenant('TENANT_ID1');

// Create a custom token in the usual manner
tenantAuth.createCustomToken(uid)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

次のコードは、カスタム トークンを使用してログインする方法を示しています。

firebase.auth().tenantId = 'TENANT_ID1';

firebase.auth().signInWithCustomToken(token)
  .catch((error) => {
    // Handle Errors here.
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

テナント ID が一致しない場合、signInWithCustomToken() メソッドは失敗します。

マルチテナント ユーザーの認証情報をリンクする

既存のマルチテナント ユーザーには、他の種類の認証情報をリンクできます。たとえば、あるテナントの SAML プロバイダでユーザーが認証済みの場合、既存のアカウントにメールアドレス / パスワードによるログインを追加して、どちらの方法でもテナントにログインできるようにできます。

// Switch to TENANT_ID1
firebase.auth().tenantId = 'TENANT_ID1';

//Sign-in with popup
firebase.auth().signInWithPopup(provider)
  .then((result) => {
    // Existing user with SAML provider.
    const user = result.user;
    const emailCredential =
        firebase.auth.EmailAuthProvider.credential(email, password);
    return user.linkWithCredential(emailCredential);
  })
  .then((linkResult) => {
    // The user can sign in with both SAML and email/password now.
  });

既存のマルチテナント ユーザーをリンクまたは再認証すると、auth.tenantId は無視されます。user.tenantId を使用して、使用するテナントを指定します。これは、updateProfileupdatePassword などの他のユーザー管理 API にも適用されます。

account-exists-with-different-credential エラーの処理

Cloud Console で [同じメールアドレスを使用するアカウントをリンクする] 設定を有効にしている場合、ユーザーが、あるプロバイダ(Google など)用の既存のメールアドレスで、別のプロバイダ(SAML など)にログインしようとすると、エラー auth/account-exists-with-different-credential が(AuthCredential オブジェクトとともに)スローされます。

目的のプロバイダでのログインを完了するには、まず既存のプロバイダ(Google)にログインしてから、次に以前の AuthCredential(SAML)にリンクする必要があります。

このエラーを処理するには、ポップアップ フローまたはリダイレクト フローのいずれかを使用します。

次の例は、signInWithPopup の使用時に auth/account-exists-with-different-credential エラーを処理する方法を示しています。

// Step 1.
// User tries to sign in to the SAML provider in that tenant.
firebase.auth().tenantId = 'TENANT_ID';
firebase.auth().signInWithPopup(samlProvider)
  .catch((error) => {
    // An error happened.
    if (error.code === 'auth/account-exists-with-different-credential') {
      // Step 2.
      // User's email already exists.
      // The pending SAML credential.
      var pendingCred = error.credential;
      // The credential's tenantId if needed: error.tenantId
      // The provider account's email address.
      var email = error.email;
      // Get sign-in methods for this email.
      firebase.auth().fetchSignInMethodsForEmail(email)
        .then((methods) => {
          // Step 3.
          // Ask the user to sign in with existing Google account.
          if (methods[0] == 'google.com') {
            firebase.auth().signInWithPopup(googleProvider)
              .then((result) => {
                // Step 4
                // Link the SAML AuthCredential to the existing user.
                result.user.linkWithCredential(pendingCred)
                  .then(function(linkResult) {
                    // SAML account successfully linked to the existing
                    // user.
                    goToApp();
                  });
              });
          }
        });
    }
  });

リダイレクト

signInWithRedirect を使用すると、リダイレクト フローの終了時に getRedirectResultauth/account-exists-with-different-credential エラーが発生します。

エラー オブジェクトには、error.tenantId プロパティが含まれます。auth インスタンスのテナント ID はリダイレクト後に保持されないため、エラー オブジェクトのテナント ID を auth インスタンスに設定する必要があります。

次の例は、エラーの処理方法を示しています。

// Step 1.
// User tries to sign in to SAML provider.
firebase.auth().tenantId = 'TENANT_ID';
firebase.auth().signInWithRedirect(samlProvider);
// Redirect back from SAML IDP. auth.tenantId is null after redirecting.
firebase.auth().getRedirectResult().catch((error) => {
  if (error.code === 'auth/account-exists-with-different-credential') {
    // Step 2.
    // User's email already exists.
    var tenantId = error.tenantId;
    // The pending SAML credential.
    var pendingCred = error.credential;
    // The provider account's email address.
    var email = error.email;
    // Need to set the tenant ID again as the page was reloaded and the
    // previous setting was reset.
    firebase.auth().tenantId = tenantId;
    // Get sign-in methods for this email.
    firebase.auth().fetchSignInMethodsForEmail(email)
      .then((methods) => {
        // Step 3.
        // Ask the user to sign in with existing Google account.
        if (methods[0] == 'google.com') {
          firebase.auth().signInWithRedirect(googleProvider);
        }
      });
  }
});

// Redirect back from Google. auth.tenantId is null after redirecting.
firebase.auth().getRedirectResult().then((result) => {
  // Step 4
  // Link the SAML AuthCredential to the existing user.
  // result.user.tenantId is 'TENANT_ID'.
  result.user.linkWithCredential(pendingCred)
    .then(function(linkResult) {
      // SAML account successfully linked to the existing
      // user.
      goToApp();
    });
});

次のステップ