Managing Identity Platform tenants programmatically

This article explains how to use the Identity Platform Admin SDK to manage tenants and their users programmatically. Some activities you can perform as an administrator include:

  • User management: Create, update, delete, and list users for a specific tenant.

  • Identity verification: Identify users of an app to restrict access to resources on your own server.

  • Import users: Migrate users from an external authentication system or another Identity Platform project or tenant.

  • Access control with custom claims: Define custom attributes on user accounts for a specific tenant and implement various access control strategies, such as role-based access control.

  • User session management: Revoke a user's refresh tokens for a specific tenant.

  • Email action links: Generate customized email links for password reset, email link sign-in, and email verification for users of a specific tenant.

  • Tenant management: Create, list, get, update, delete tenants for a specific Identity Platform project.

  • Manage OIDC and SAML providers on tenants: Programmatically manage OIDC and SAML configurations on a specified tenant.

Before you begin

Supported features

The following table lists the features supported by each SDK in a multi-tenant environment:

Feature Node.js Java Python Go C#
Custom token minting
Verifying ID tokens
Managing users
Controlling access with custom claims
Revoking refresh tokens
Importing users
Generating email action links
Managing SAML/OIDC provider configurations
Session cookie management

The following table shows what sign-in methods you can configure using the Admin SDK and the Cloud Console in a tenant-specific context:

Feature Cloud Console Admin SDK
Email
OIDC
SAML
Social
Phone
Multi-factor authentication
Anonymous

Tenant management

Using the Admin SDK, you can manage tenants programmatically from a secure server environment instead of using the Cloud Console. This includes the ability to create, list, get, modify, or delete tenants.

Each tenant contains its own identity providers, settings, and sets of users. Tenant configuration management operations (CRUD) are available from the parent project instance using admin.auth().tenantManager().

A tenant configuration provides information about a tenant, such as its display name, tenant identifier, and email authentication configuration.

All other settings (such as whitelisted domains and authenticated redirect URIs) of a tenant are inherited from the parent project. These must be managed using the Cloud Console.

For operations such as tenant-specific user management, configuring OIDC/SAML providers, and email link generation, you'll need a TenantAwareAuth instance for the target tenant (identified by its unique tenantId).

const tenantManager = admin.auth().tenantManager();
const tenantAuth = tenantManager.authForTenant(tenantId);

All calls to the user management APIs, OIDC/SAML provider management APIs, and email link generation APIs will be inside the scope of this tenant (using its TenantAwareAuth instance).

Getting an existing tenant

The Admin SDK provides the getTenant() method, which fetches information about a tenant based on its tenantId (a unique identifier for the tenant).

admin.auth().tenantManager().getTenant(tenantId)
  .then((tenant) => {
    console.log(tenant.toJSON());
  })
  .catch((error) => {
    // Handle error.
  });

This method returns a Tenant object corresponding to the tenantId. If the provided tenantId does not belong to an existing tenant, the returned promise rejects with an auth/tenant-not-found error.

Be careful not to confuse a Tenant instance with a TenantAwareAuth object. authInstance.tenantManager().authForTenant() returns a TenantAwareAuth instance that extends BaseAuth. The Auth class also extends BaseAuth. BaseAuth provides APIs to manage users, configure OIDC/SAML providers in different contexts. For Auth, the context is at the parent project level. For TenantAwareAuth, the context is at the tenant level (the tenant is determined by the tenant ID). The getTenant() method will resolve with basic tenant information (like tenant ID, display name, and email provider settings), but to call APIs on that tenant, you need to use authForTenant(tenantFromGetTenant.tenantId).

Creating a tenant

Use the createTenant() method to to create a new tenant configuration:

admin.auth().tenantManager().createTenant({
  displayName: 'myTenant1',
  emailSignInConfig: {
    enabled: true,
    passwordRequired: false, // Email link sign-in enabled.
  },
})
.then((createdTenant) => {
  console.log(createdTenant.toJSON());
})
.catch((error) => {
  // Handle error.
});

You can provide any combination of these properties:

Property Type Description
displayName
string
The tenant display name. This must be 4-20 characters, consisting of letters, digits, and hyphens, and must begin with a letter.
emailSignInConfig
{
  enable: boolean,
  passwordRequired: boolean
}
    
The email sign in provider configuration. This includes whether email provider is enabled, and whether password is required for email sign-in. When not required, email sign-in can be performed with password or using email link sign-in.

The method returns a Tenant object for the newly created tenant.

Updating a tenant

Use the updateTenant() method to modify an existing tenant's data. You'll need to specify the tenantId, along with the properties to update for that tenant.

admin.auth().tenantManager().updateTenant(tenantId, {
  displayName: 'updatedName',
  emailSignInConfig: {
    enabled: false, // Disable email provider.
  },
})
.then((updatedTenant) => {
  console.log(updatedTenant.toJSON());
})
.catch((error) => {
  // Handle error.
});

updateTenant() accepts the same properties as createTenant(). All properties are optional. If a property is not specified, the existing value will not be modified.

The method returns an updated Tenant object upon completion. If the provided tenantId does not belong to an existing tenant, the returned promise rejects with an auth/tenant-not-found error.

Deleting a tenant

You can delete a tenant using its tenantId:

admin.auth().tenantManager().deleteTenant(tenantId)
  .then(() => {
    // Tenant deleted.
  })
  .catch((error) => {
    // Handle error.
  });

The method returns an empty result when the deletion completes successfully. If the provided tenantId does not belong to an existing tenant, the returned promise rejects with an auth/tenant-not-found error.

Listing tenants

Use the listTenants() method to list existing tenants:

function listAllTenants(nextPageToken) {
  return admin.auth().tenantManager().listTenants(100, nextPageToken)
    .then((result) => {
      result.tenants.forEach((tenant) => {
        console.log(tenant.toJSON());
      });
      if (result.pageToken) {
        return listAllTenants(result.pageToken);
      }
    });
}

listAllTenants();

Each batch of results contains a list of tenants, plus a next page token to list the next batch of tenant. When all the tenants have already been listed, no pageToken is returned.

If no maxResults field is specified, the default is 1000 tenants per batch. This is also the maximum number of tenants allowed to be listed at a time. Any value greater than the maximum will throw an argument error. If no pageToken is specified, the method will list tenants from the beginning.

Managing SAML and OIDC providers programmatically

The Admin SDK provides APIs for managing Security Assertion Markup Language (SAML) 2.0 and OpenID Connect (OIDC) provider configurations programmatically from a secure server environment.

With the Admin SDK, you can manage these providers for a specific tenant. This is similar to managing project-level OIDC and SAML providers.

To manage providers for a tenant, first create a TenantAwareAuth instance:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

You can then perform common operations, such as creating, modifying, or deleting providers for a tenant.

Creating a provider

The following code shows how to create a SAML provider for a tenant:

const newConfig = {
  displayName: 'SAML provider name',
  enabled: true,
  providerId: 'saml.myProvider',
  idpEntityId: 'IDP_ENTITY_ID',
  ssoURL: 'https://example.com/saml/sso/1234/'
  x509Certificates: [
    '-----BEGIN CERTIFICATE-----\nCERT1...\n-----END CERTIFICATE-----',
    '-----BEGIN CERTIFICATE-----\nCERT2...\n-----END CERTIFICATE-----',
  ],
  rpEntityId: 'RP_ENTITY_ID',
  // Using the default callback URL.
  callbackURL: 'https://project-id.firebaseapp.com/__/auth/handler',
};

tenantAuth.createProviderConfig(newConfig).then(() => {
  // Successful creation.
}).catch((error) => {
  // Handle error.
});

Modifying a provider

The following code shows how to modify a provider:

const updatedConfig = {
  x509Certificates: [
    '-----BEGIN CERTIFICATE-----\nCERT2...\n-----END CERTIFICATE-----',
    '-----BEGIN CERTIFICATE-----\nCERT3...\n-----END CERTIFICATE-----',
  ],
};
tenantAuth.updateProviderConfig('saml.myProvider', updatedConfig).then(() => {
  // Successful update.
}).catch((error) => {
  // Handle error.
});

Getting a provider

The following code shows how to retrieve the provider configuration for a specific tenant using their provider ID:

tenantAuth.getProviderConfig('saml.myProvider').then((config) => {
  // Get display name and whether it is enabled.
   console.log(config.displayName, config.enabled);
}).catch((error) => {
  // Handle error. Common error is that config is not found.
});

Listing providers

The following code shows how to list provider configurations for a given tenant:

// Returns 10 SAML provider configs starting from the specified nextPageToken offset.
tenantAuth.listProviderConfigs({type: 'saml', maxResults: 10, pageToken: 'nextPageToken'}).then((results) => {
  results.providerConfigs.forEach((config) => {
     console.log(config.providerId);
  });
  // To list the next 10:
  // return tenantAuth.listProviderConfigs(
  //     {type: 'saml', maxResults: 10, pageToken: results.pageToken});
}).catch((error) => {
   // Handle error.
});

Deleting a provider

The following code shows how to delete a provider:

tenantAuth.deleteProviderConfig('saml.myProvider').then(() => {
  // Successful deletion.
}).catch((error) => {
  // Handle error.
});

OIDC providers are managed similarly to project-level OIDC providers, except they can be managed from the corresponding TenantAwareAuth instance, rather than an Auth project-level instance.

To learn more, refer to Managing SAML and OIDC providers programmatically.

Managing tenant specific users

You can use the Admin SDK to to create, retrieve, update, delete, and list all users for a specific tenant.

To start, you need a TenantAwareAuth instance for the corresponding tenant:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

Getting a user

You can retrieve a tenant-specific user with a uid identifier:

tenantAuth.getUser(uid)
  .then((userRecord) => {
    // See the UserRecord reference documentation to learn more.
    console.log('Successfully fetched user data:', userRecord.toJSON());
    // Tenant ID will be reflected in userRecord.tenantId.
  })
  .catch((error) => {
    console.log('Error fetching user data:', error);
  });

You can also identify a user by their email:

tenantAuth.getUserByEmail(email)
  .then((userRecord) => {
    // See the UserRecord reference documentation to learn more.
    console.log('Successfully fetched user data:', userRecord.toJSON());
    // Tenant ID will be reflected in userRecord.tenantId.
  })
  .catch((error) => {
    console.log('Error fetching user data:', error);
  });

Creating a user

Use the createUser() method to create new users for a specific tenant. When creating a new user, providing a uid is optional; if not specified, Identity Platform will provision a unique one.

tenantAuth.createUser({
  email: 'user@example.com',
  emailVerified: false,
  phoneNumber: '+11234567890',
  password: 'secretPassword',
  displayName: 'John Doe',
  photoURL: 'http://www.example.com/12345678/photo.png',
  disabled: false
})
.then((userRecord) => {
  // See the UserRecord reference documentation to learn more.
  console.log('Successfully created new user:', userRecord.uid);
  // Tenant ID will be reflected in userRecord.tenantId.
})
.catch((error) => {
  console.log('Error creating new user:', error);
});

Modifying a user

You can modify existing users by specifying their uid to the updateUser() method:

tenantAuth.updateUser(uid, {
  email: 'modifiedUser@example.com',
  phoneNumber: '+11234567890',
  emailVerified: true,
  password: 'newPassword',
  displayName: 'Jane Doe',
  photoURL: 'http://www.example.com/12345678/photo.png',
  disabled: true
})
.then((userRecord) => {
  // See the UserRecord reference documentation to learn more.
  console.log('Successfully updated user', userRecord.toJSON());
})
.catch((error) => {
  console.log('Error updating user:', error);
});

Deleting a user

The following example shows how to delete a user based on their uid:

tenantAuth.deleteUser(uid)
  .then(() => {
    console.log('Successfully deleted user');
  })
  .catch((error) => {
    console.log('Error deleting user:', error);
  });

Listing users

To retrieve an entire list of users for a specific tenant in batches, use the listUsers() method. Each batch will contain a list of user records, plus a next page token if additional users remain.

function listAllUsers(nextPageToken) {
  // List batch of users, 1000 at a time.
  tenantAuth.listUsers(1000, nextPageToken)
    .then((listUsersResult) => {
      listUsersResult.users.forEach((userRecord) => {
        console.log('user', userRecord.toJSON());
        // Tenant ID will be reflected in userRecord.tenantId.
      });
      if (listUsersResult.pageToken) {
        // List next batch of users.
        listAllUsers(listUsersResult.pageToken);
      }
    })
    .catch((error) => {
      console.log('Error listing users:', error);
    });
}
// Start listing users from the beginning, 1000 at a time.
listAllUsers();

See the Admin SDK documentation on managing users to learn more.

Importing users

You can use the Admin SDK to import users in bulk to a specific tenant with elevated privileges. This offers numerous benefits, such as the ability to migrate users from another Identity Platform product, from another tenant, or from an an external authentication system using a different hashing algorithm. You can also import users with federated providers (such as SAML and OIDC) and custom claims directly in bulk.

To start, get a TenantAwareAuth instance for the corresponding tenant:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

You can import up to 1000 users at a time using a specific hashing algorithm.

tenantAuth.importUsers([{
  uid: 'uid1',
  email: 'user1@example.com',
  // Must be provided in a byte buffer.
  passwordHash: Buffer.from('password-hash-1'),
  // Must be provided in a byte buffer.
  passwordSalt: Buffer.from('salt1')
},
{
  uid: 'uid2',
  email: 'user2@example.com',
  // Must be provided in a byte buffer.
  passwordHash: Buffer.from('password-hash-2'),
  // Must be provided in a byte buffer.
  passwordSalt: Buffer.from('salt2')

}], {
  hash: {
    algorithm: 'HMAC_SHA256',
    // Must be provided in a byte buffer.
    key: Buffer.from('secret')
  }
})
.then((results) => {
  results.errors.forEach(function(indexedError) {
   console.log('Error importing user ' + indexedError.index);
  });
})
.catch((error) => {
  console.log('Error importing users:', error);
});

All imported users will have their tenantId set to the tenantAuth.tenantId.

Users without passwords can also be imported to a specific tenant. These users can be imported with federated providers and custom claims.

tenantAuth.importUsers([{
  uid: 'some-uid',
  displayName: 'John Doe',
  email: 'johndoe@acme.com',
  photoURL: 'http://www.example.com/12345678/photo.png',
  emailVerified: true,
  phoneNumber: '+11234567890',
  // Set this user as admin.
  customClaims: {admin: true},
  // User with SAML provider.
  providerData: [{
    uid: 'saml-uid',
    email: 'johndoe@acme.com',
    displayName: 'John Doe',
    photoURL: 'http://www.example.com/12345678/photo.png',
    providerId: 'saml.acme'
  }]
}])
.then(function(results) {
  results.errors.forEach(function(indexedError) {
   console.log('Error importing user ' + indexedError.index);
  });
})
.catch(function(error) {
  console.log('Error importing users:', error);
});

See Import users in the Admin SDK documentation to learn more.

Identity verification

When an Identity Platform client app communicates with a custom backend server, the current signed in user needs to be identified on that server. This can be done securely by sending the user's ID token after successful sign-in using a secure connection to your server. The server can then verify the integrity and authenticity of the ID token.

The Admin SDK has a built-in method for verifying and decoding ID tokens for a specific tenant.

After successfully signing in a user to a specific tenant from the client, retrieve the user's ID token using the Client SDK:

auth.tenantId = 'TENANT-ID';
auth.signInWithEmailAndPassword('user@example.com', 'password')
  .then((userCredential) => {
    return userCredential.user.getIdToken();
  })
  .then((idToken) => {
    // Send the ID token to server for verification. ID token should be scoped to TENANT-ID.
  });

Create a TenantAwareAuth instance on the server:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

You can then verify the ID token for that specific tenant:

// idToken comes from the client app
tenantAuth.verifyIdToken(idToken)
  .then((decodedToken) => {
    let uid = decodedToken.uid;
    // This should be set to TENANT-ID. Otherwise auth/mismatching-tenant-id error thrown.
    console.log(decodedToken.firebase.tenant);
    // ...
  }).catch((error) => {
    // Handle error
  });

A server side resource may be accessible by multiple tenants with different levels of access. Since the tenant ID may not be known ahead of time in this case, the ID token can be verified at the project level first.

admin.auth().verifyIdToken(idToken)
  .then((decodedToken) => {
    if (decodedToken.firebase.tenant === 'TENANT-ID1') {
      // Allow appropriate level of access for TENANT-ID1.
    } else if (decodedToken.firebase.tenant === 'TENANT-ID2') {
      // Allow appropriate level of access for TENANT-ID2.
    } else {
      // Block access for all other tenants.
      throw new Error('Access not allowed.');
    }
  }).catch((error) => {
    // Handle error
  });

See the Admin SDK article on verifying ID tokens to learn more.

Managing user sessions

Identity Platform sessions are long lived. Every time a user signs in, the user's credentials are verified on the Identity Platform server, then exchanged for a short lived ID token and a long lived refresh token. An ID tokens last for an hour. Refresh tokens never expire, except when a user is disabled, deleted or undergoes a large account change (like an email or password update).

In some cases, a user's refresh token may need to be revoked for security reasons, such as the user reporting a lost or stolen device, discovery of a general vulnerability within an app, or a wide-scale leak of active tokens. The Admin SDK provides an API to revoke all issued refresh tokens for a specified user of a specific tenant.

To begin, you need a TenantAwareAuth instance:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

The refresh tokens can then be revoked by specifying the uid of that user:

// Revoke all refresh tokens for a specified user in a specified tenant for whatever reason.
// Retrieve the timestamp of the revocation, in seconds since the epoch.
tenantAuth.revokeRefreshTokens(uid)
  .then(() => {
    return tenantAuth.getUser(uid);
  })
  .then((userRecord) => {
    return new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
  })
  .then((timestamp) => {
    console.log('Tokens revoked at: ', timestamp);
  });

After refresh tokens are revoked, no new ID tokens can be issued for that user until they re-authenticate. However, existing ID tokens will remain active until their natural expiration time (one hour).

You can verify that an unexpired valid ID token is not revoked by specifying the optional checkRevoked parameter; this checks if a token is revoked after its integrity and authenticity is verified.

// Verify the ID token for a specific tenant while checking if the token is revoked by passing
// checkRevoked true.
let checkRevoked = true;
tenantAuth.verifyIdToken(idToken, checkRevoked)
  .then(payload => {
    // Token is valid.
  })
  .catch(error => {
    if (error.code == 'auth/id-token-revoked') {
      // Token has been revoked. Inform the user to re-authenticate or
      // signOut() the user.
    } else {
      // Token is invalid.
    }
  });

See the Admin SDK documentation on managing sessions to learn more.

Controlling access with custom claims

The Admin SDK supports defining custom attributes on user accounts for a specific tenant. These attributes allow you to implement different access control strategies, such as role-based access control. The attributes can be used to give users different levels of access enforced by the application's security rules.

To begin, get a TenantAwareAuth instance for the corresponding tenant:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

Custom claims can contain sensitive data, so they should only be set from a privileged server environment using the Admin SDK.

// Set admin privilege on the user corresponding to uid for a specific tenant.
tenantAuth.setCustomUserClaims(uid, {admin: true}).then(() => {
  // The new custom claims will propagate to the user's ID token the
  // next time a new one is issued.
});

Newly-set custom attributes will appear on the top-level attributes of the token payload the next time the user signs in or refreshes their ID tokens on an existing session, In the previous example, the ID token contains an additional claim: {admin: true}.

After verifying the ID token and decoding its payload, the additional custom claims can then be checked to enforce access control.

// Verify the ID token first.
tenantAuth.verifyIdToken(idToken).then((claims) => {
  if (claims.admin === true) {
    // Allow access to requested admin resource.
  }
});

Custom claims for an existing user for a specific tenant are also available as a property on the user record.

// Lookup the user associated with the specified uid.
tenantAuth.getUser(uid).then((userRecord) => {
  // The claims can be accessed on the user record.
  console.log(userRecord.customClaims.admin);
});

See the Admin SDK documentation on Custom claims to learn more.

Using the Identity Platform Client SDKs, you can send users of a specific tenant emails containing links they can use for password resets, email address verification, and email-based sign-in. These emails are sent by Google and have limited customizability.

With the Admin SDK, you can generate these links programmatically within the scope of a specific tenant.

To begin, get a TenantAwareAuth instance for the corresponding tenant:

const tenantAuth = admin.auth().tenantManager().authForTenant('TENANT-ID');

The following example shows how to generate a link to verify a user's email for a specified tenant:

const actionCodeSettings = {
  // URL you want to redirect back to. The domain (www.example.com) for
  // this URL must be whitelisted in the Cloud Console.
  url: 'https://www.example.com/checkout?cartId=1234',
  // This must be true for email link sign-in.
  handleCodeInApp: true,
  iOS: {
    bundleId: 'com.example.ios'
  },
  android: {
    packageName: 'com.example.android',
    installApp: true,
    minimumVersion: '12'
  },
  // FDL custom domain.
  dynamicLinkDomain: 'coolapp.page.link'
};

// Admin SDK API to generate the email verification link.
const userEmail = 'user@example.com';
tenantAuth.generateEmailVerificationLink(userEmail, actionCodeSettings)
  .then((link) => {
    // Construct email verification template, embed the link and send
    // using custom SMTP server.
    return sendCustomVerificationEmail(userEmail, displayName, link);
  })
  .catch((error) => {
    // Some error occurred.
  });

Similar mechanisms are available for generating password reset and email-based sign-in links. Note that when generating an email action link in a tenant context, the tenant ID has to be parsed from the link and set on the client Auth instance before the code can be applied.

const actionCodeUrl = firebase.auth.ActionCodeURL.parseLink(window.location.href);
// A one-time code, used to identify and verify a request.
const code = actionCodeUrl.code;
// The tenant ID being used to trigger the email action.
const tenantId = actionCodeUrl.tenantId;
auth.tenantId = tenantId;

// Apply the action code.
auth.applyActionCode(actionCode)
  .then(() => {
    // User's email is now verified.
  })
  .catch((error) => {
    // Handle error.
  });

See Email action links in the Admin SDK documentation to learn more.

Error messages

The following table lists common error messages you may encounter.

Error code Description and Resolution Steps
auth/billing-not-enabled This feature requires billing to be enabled.
auth/invalid-display-name The displayName field must be a valid string.
auth/invalid-name The resource name provided is invalid.
auth/invalid-page-token The page token must be a valid non-empty string.
auth/invalid-project-id Invalid parent project. Either the parent project doesn't, or didn't enable multi-tenancy.
auth/invalid-tenant-id The tenant ID must be a valid non-empty string.
auth/mismatching-tenant-id User tenant ID does not match with the current TenantAwareAuth tenant ID.
auth/missing-display-name The resource being created or edited is missing a valid display name.
auth/insufficient-permission The user has insufficient permission to access the requested resource or to run the specific tenant operation.
auth/quota-exceeded The project quota for the specified operation has been exceeded.
auth/tenant-not-found There is no tenant corresponding to the provided identifier.
auth/unsupported-tenant-operation This operation is not supported in a multi-tenant context.