Authenticating with multi-tenancy

This document shows you how to authenticate users in a multi-tenant Identity Platform environment.

Before you begin

Make sure you've enabled multi-tenancy for your project and configured your tenants. See Getting started with multi-tenancy to learn how.

You'll also need to add the Client SDK to your app:

  1. Go to the Identity Platform page in the Cloud Console.
    Go to the Identity Platform users page

  2. On the top right, click Application setup details.

  3. Copy the code into your web app. For example:

    <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>
    

Sign in with tenants

To sign in to a tenant, the tenant ID needs to be passed to the auth object. Note that tenantId is not persisted on page reloads.

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

Any future sign-in requests from this auth instance will include the tenant ID (TENANT_ID1 in the preceding example) until you change or reset the tenant ID.

You can work with multiple tenants using single or multiple auth instances.

To use a single auth instance, modify the tenantId property whenever you want to switch between tenants. To revert to back to project-level IdPs, set tenantId to null:

// 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;

To use multiple instances, create a new auth instance for each tenant and assign them different IDs:

// 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';

After signing in with a tenant, a tenant user will be returned with user.tenantId set to that tenant. Note that if you switch tenantId on the auth instance later, the currentUser property will not change; it will still point to the same user as the previous tenant.

// 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.
  }
});

Email/password accounts

The following example shows how to register a new user:

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

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

To sign in an existing user:

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

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

SAML

To sign in with a SAML provider, instantiate a SAMLAuthProvider instance with the provider ID from the Cloud Console:

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

You can then use either a popup or a redirect flow to sign in to the SAML provider.

The following example shows the popup flow:

// 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.
  });

Redirect

The following example shows the redirect flow:

// 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.
  });

In both cases, be sure to set the correct tenant ID on the auth instance.

To initiate the authentication flow, display an interface prompting the user to provide their email address, then call sendSignInLinkToEmail to send them an authentication link. Make sure to set the correct tenant ID on the auth instance before sending the email.

// 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
  });

To complete sign-in on the landing page, first parse the tenant ID from the email link and set it on the auth instance. Then call signInWithEmailLink with the user's email and the actual email link containing the one-time code.

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.
    });
}

Creating custom tokens

Creating a multi-tenant aware custom token is identical to creating a regular custom token; as long as the correct tenant ID has been set on the auth instance, a top-level tenant_id claim will be added to the resulting JWT. See Creating custom tokens for detailed instructions on how to create and use custom tokens.

The following example shows how to create a custom token using the 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);
  });

The following code demonstrates how to sign in using a custom token:

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

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

Note that if the tenant IDs do not match, the signInWithCustomToken() method will fail.

Linking multi-tenant user credentials

You can link other types of credentials to an existing multi-tenant user. For example, if a user previously authenticated with a SAML provider in a tenant, you can add email/password sign-in to their existing account so they can use either method to sign in to the tenant.

// 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.
  });

When linking or re-authenticating an existing multi-tenant user, auth.tenantId will be ignored; use user.tenantId to specify which tenant to use. This also applies to other user management APIs, such as updateProfile and updatePassword.

Handling the account-exists-with-different-credential errors

If you enabled the Link accounts that use the same email setting in Cloud Console, when a user tries to sign in to a provider (such as SAML) with an email that already exists for another provider (such as Google), the error auth/account-exists-with-different-credential is thrown (along with an AuthCredential object).

To finish signing in with the intended provider, the user must first sign in to the existing provider (Google), then link to the former AuthCredential (SAML).

You can use either a popup or redirect flow to handle this error.

The following example shows how to handle auth/account-exists-with-different-credential errors when using signInWithPopup:

// 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();
                  });
              });
          }
        });
    }
  });

Redirect

When using signInWithRedirect, auth/account-exists-with-different-credential errors will be raised in getRedirectResult when finishing the redirect flow.

The error object contains the property error.tenantId. Since the tenant ID on the auth instance is not persisted after redirecting, you'll need to set the tenant ID from the error object to the auth instance.

The following example shows how to handle the error:

// 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();
    });
});

What's next