以程式輔助方式管理 Identity Platform 租戶

本文說明如何使用 Identity Platform Admin SDK,以程式輔助方式管理租用戶及其使用者。您可以以管理員身分執行的部分活動包括:

  • 使用者管理:建立、更新、刪除及列出特定租用戶的使用者。

  • 身分驗證:識別應用程式的使用者,以便限制存取您自有伺服器上的資源。

  • 匯入使用者:從外部驗證系統或其他 Identity Platform 專案或租用戶遷移使用者。

  • 使用自訂權利要求的存取權控管:針對特定租用戶在使用者帳戶中定義自訂屬性,並實作各種存取權控管策略,例如角色型存取權控管。

  • 使用者工作階段管理:針對特定租用戶撤銷使用者的重新整理權杖。

  • 電子郵件動作連結:為特定租用戶的使用者產生自訂電子郵件連結,用於重設密碼、透過電子郵件連結登入,以及電子郵件驗證。

  • 用戶群管理:建立、列出、取得、更新及刪除特定 Identity Platform 專案的用戶群。

  • 管理租用戶的 OIDC 和 SAML 供應器:透過程式輔助方式管理指定租用戶的 OIDC 和 SAML 設定。

事前準備

支援功能

下表列出多租戶環境中各 SDK 支援的功能:

功能 Node.js Java Python Go C#
自訂代幣鑄造
驗證 ID 權杖
管理使用者
使用自訂宣告控管存取權
撤銷更新權杖
匯入使用者
產生電子郵件動作連結
多重驗證
管理 SAML/OIDC 供應器設定
工作階段 Cookie 管理

下表列出您可以在租用戶專屬情境中,使用 Admin SDK 和 Google Cloud 主控台設定的登入方法:

功能 Google Cloud 控制台 Admin SDK
電子郵件地址
OIDC
SAML
社交
手機
多重驗證
匿名

租戶管理

您可以使用 Admin SDK,透過程式輔助方式從安全的伺服器環境管理租用戶,而非使用 Google Cloud 控制台。包括建立、列出、取得、修改或刪除租用戶的權限。

每個用戶群都有自己的識別資訊提供者、設定和一組使用者。使用 admin.auth().tenantManager() 時,可透過父級專案執行個體執行租用戶設定管理作業 (CRUD)。

租用戶設定會提供租用戶的相關資訊,例如顯示名稱、租用戶 ID 和電子郵件驗證設定。

用戶群的所有其他設定 (例如許可網域和已驗證的重新導向 URI) 均會繼承自父項專案。必須使用 Google Cloud 控制台管理這些項目。

針對特定租用戶的使用者管理、設定 OIDC/SAML 供應器和產生電子郵件連結等作業,您需要目標租用戶的 TenantAwareAuth 例項 (由其專屬 tenantId 識別)。

Node.js

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

Python

from firebase_admin import tenant_mgt

tenant_client = tenant_mgt.auth_for_tenant(tenant_id)

Java

FirebaseAuth auth = FirebaseAuth.getInstance();
TenantManager tenantManager = auth.getTenantManager();
TenantAwareFirebaseAuth tenantAuth = tenantManager.getAuthForTenant(tenantId);

所有對使用者管理 API、OIDC/SAML 供應器管理 API 和電子郵件連結產生 API 的呼叫,都會在這個租用戶的範圍內 (使用其 TenantAwareAuth 例項)。

取得現有租用戶

Admin SDK 提供 getTenant() 方法,可根據租用戶的 tenantId (租用戶專屬 ID) 擷取租用戶相關資訊。

Node.js

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

Python

tenant = tenant_mgt.get_tenant(tenant_id)

print('Retreieved tenant:', tenant.tenant_id)

Java

Tenant tenant = FirebaseAuth.getInstance().getTenantManager().getTenant(tenantId);
System.out.println("Retrieved tenant: " + tenant.getTenantId());

這個方法會傳回與 tenantId 對應的 Tenant 物件。如果提供的 tenantId 不屬於現有的租用戶,則傳回的承諾會拒絕,並顯示 auth/tenant-not-found 錯誤。

請注意,不要將 Tenant 例項與 TenantAwareAuth 物件混淆。authInstance.tenantManager().authForTenant() 會傳回擴充 BaseAuthTenantAwareAuth 例項。Auth 類別也會擴充 BaseAuthBaseAuth 提供 API,可在不同情境中管理使用者、設定 OIDC/SAML 供應器。對於 Auth,情境位於父專案層級。對於 TenantAwareAuth,情境位於租用戶層級 (租用戶由租用戶 ID 決定)。getTenant() 方法會使用基本租用戶資訊 (例如租用戶 ID、顯示名稱和電子郵件供應器設定) 進行解析,但如果要呼叫該租用戶的 API,您必須使用 authForTenant(tenantFromGetTenant.tenantId)

建立租戶

使用 createTenant() 方法建立新的租用戶設定:

Node.js

admin.auth().tenantManager().createTenant({
  displayName: 'myTenant1',
  emailSignInConfig: {
    enabled: true,
    passwordRequired: false, // Email link sign-in enabled.
  },
  // TODO: Remove if you don't want to enable multi-factor authentication.
  multiFactorConfig: {
    state: 'ENABLED',
    factorIds: ['phone']
  },
  // TODO: Remove if you don't want to register test phone numbers for use
  // with multi-factor authentication.
  testPhoneNumbers: {
    '+16505551234': '145678',
    '+16505550000': '123456'
  },
})
.then((createdTenant) => {
  console.log(createdTenant.toJSON());
})
.catch((error) => {
  // Handle error.
});

Python

tenant = tenant_mgt.create_tenant(
    display_name='myTenant1',
    enable_email_link_sign_in=True,
    allow_password_sign_up=True)

print('Created tenant:', tenant.tenant_id)

Java

Tenant.CreateRequest request = new Tenant.CreateRequest()
    .setDisplayName("myTenant1")
    .setEmailLinkSignInEnabled(true)
    .setPasswordSignInAllowed(true);
Tenant tenant = FirebaseAuth.getInstance().getTenantManager().createTenant(request);
System.out.println("Created tenant: " + tenant.getTenantId());

您可以提供下列屬性的任意組合:

屬性 類型 說明
displayName
string
    
租用戶顯示名稱。長度必須介於 4 到 20 個半形字元之間,且只能由英文字母、數字和連字號組成,開頭須為英文字母。
emailSignInConfig
{
  enable: boolean,
  passwordRequired: boolean
}
    
電子郵件登入服務供應商設定。包括是否啟用電子郵件供應器,以及是否需要密碼才能登入電子郵件。在不需要密碼的情況下,您可以使用密碼或電子郵件連結登入。
multiFactorConfig
{
  state: 'DISABLED' | 'ENABLED',
  factorIds: string[]
}
    
是否為用戶群啟用多重驗證,以及允許哪些驗證因素類型。目前唯一支援的因素 ID 是 phone
testPhoneNumbers
{
  string: string
}
  
電話號碼和相關多重驗證碼的對應表,用於測試用途註冊。最多可輸入 10 個項目。如要移除所有測試電話號碼,請將這個欄位設為 null

這個方法會傳回新建立租用戶的 Tenant 物件。

更新用戶群

使用 updateTenant() 方法修改現有租用戶的資料。您必須指定 tenantId,以及要為該租戶更新的屬性。

Node.js

admin.auth().tenantManager().updateTenant(tenantId, {
  displayName: 'updatedName',
  emailSignInConfig: {
    enabled: false, // Disable email provider.
  },
  // Enable multi-factor authentication.
  multiFactorConfig: {
    state: 'ENABLED',
    factorIds: ['phone']
  },
  // Register phone numbers for testing.
  testPhoneNumbers: {
    '+16505551234': '145678',
    '+16505550000': '123456'
  },
})
.then((updatedTenant) => {
  console.log(updatedTenant.toJSON());
})
.catch((error) => {
  // Handle error.
});

Python

tenant = tenant_mgt.update_tenant(
    tenant_id,
    display_name='updatedName',
    allow_password_sign_up=False) # Disable email provider

print('Updated tenant:', tenant.tenant_id)

Java

Tenant.UpdateRequest request = new Tenant.UpdateRequest(tenantId)
    .setDisplayName("updatedName")
    .setPasswordSignInAllowed(false);
Tenant tenant = FirebaseAuth.getInstance().getTenantManager().updateTenant(request);
System.out.println("Updated tenant: " + tenant.getTenantId());

updateTenant() 可接受與 createTenant() 相同的屬性。所有屬性皆為選用。如未指定屬性,系統就不會修改現有值。

方法會在完成時傳回更新後的 Tenant 物件。如果提供的 tenantId 不屬於現有的租用戶,則傳回的承諾會拒絕,並傳回 auth/tenant-not-found 錯誤。

刪除用戶群

您可以使用租用戶的 tenantId 刪除租用戶:

Node.js

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

Python

tenant_mgt.delete_tenant(tenant_id)

Java

FirebaseAuth.getInstance().getTenantManager().deleteTenant(tenantId);

刪除作業完成後,這個方法會傳回空白結果。如果提供的 tenantId 不屬於現有的租用戶,則傳回的承諾會拒絕,並顯示 auth/tenant-not-found 錯誤。

列出用戶群

使用 listTenants() 方法列出現有的租用戶:

Node.js

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

Python

for tenant in tenant_mgt.list_tenants().iterate_all():
    print('Retrieved tenant:', tenant.tenant_id)

Java

ListTenantsPage page = FirebaseAuth.getInstance().getTenantManager().listTenants(null);
for (Tenant tenant : page.iterateAll()) {
  System.out.println("Retrieved tenant: " + tenant.getTenantId());
}

每批結果都包含租戶清單,以及用於列出下一批租戶的下一頁符記。當所有租戶都已列出時,系統不會傳回 pageToken

如未指定 maxResults 欄位,每批作業的租用戶數量預設為 1000 個。這也是一次最多可列出的房客數量。任何大於上限的值都會擲回引數錯誤。如果未指定 pageToken,方法會從開頭列出租戶。

透過程式管理 SAML 和 OIDC 供應商

Admin SDK 提供 API,可透過程式輔助方式,從安全的伺服器環境管理安全宣告標記語言 (SAML) 2.0 和 OpenID Connect (OIDC) 供應商設定。

您可以使用 Admin SDK 管理特定租用戶的這些供應商。這與管理專案層級 OIDC 和 SAML 供應商類似。

如要管理租用戶的供應者,請先建立 TenantAwareAuth 例項:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

接著,您可以執行常見作業,例如為租用戶建立、修改或刪除供應者。

建立提供者

以下程式碼顯示如何為租用戶建立 SAML 供應器:

Node.js

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

Python

saml = tenant_client.create_saml_provider_config(
    display_name='SAML provider name',
    enabled=True,
    provider_id='saml.myProvider',
    idp_entity_id='IDP_ENTITY_ID',
    sso_url='https://example.com/saml/sso/1234/',
    x509_certificates=[
        '-----BEGIN CERTIFICATE-----\nCERT1...\n-----END CERTIFICATE-----',
        '-----BEGIN CERTIFICATE-----\nCERT2...\n-----END CERTIFICATE-----',
    ],
    rp_entity_id='P_ENTITY_ID',
    callback_url='https://project-id.firebaseapp.com/__/auth/handler')

print('Created new SAML provider:', saml.provider_id)

Java

SamlProviderConfig.CreateRequest request = new SamlProviderConfig.CreateRequest()
    .setDisplayName("SAML provider name")
    .setEnabled(true)
    .setProviderId("saml.myProvider")
    .setIdpEntityId("IDP_ENTITY_ID")
    .setSsoUrl("https://example.com/saml/sso/1234/")
    .addX509Certificate("-----BEGIN CERTIFICATE-----\nCERT1...\n-----END CERTIFICATE-----")
    .addX509Certificate("-----BEGIN CERTIFICATE-----\nCERT2...\n-----END CERTIFICATE-----")
    .setRpEntityId("RP_ENTITY_ID")
    .setCallbackUrl("https://project-id.firebaseapp.com/__/auth/handler");
SamlProviderConfig saml = tenantAuth.createSamlProviderConfig(request);
System.out.println("Created new SAML provider: " + saml.getProviderId());

修改供應者

以下程式碼說明如何修改提供者:

Node.js

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

Python

saml = tenant_client.update_saml_provider_config(
    'saml.myProvider',
    x509_certificates=[
        '-----BEGIN CERTIFICATE-----\nCERT2...\n-----END CERTIFICATE-----',
        '-----BEGIN CERTIFICATE-----\nCERT3...\n-----END CERTIFICATE-----',
    ])

print('Updated SAML provider:', saml.provider_id)

Java

SamlProviderConfig.UpdateRequest request =
    new SamlProviderConfig.UpdateRequest("saml.myProvider")
      .addX509Certificate("-----BEGIN CERTIFICATE-----\nCERT2...\n-----END CERTIFICATE-----")
      .addX509Certificate("-----BEGIN CERTIFICATE-----\nCERT3...\n-----END CERTIFICATE-----");
SamlProviderConfig saml = tenantAuth.updateSamlProviderConfig(request);
System.out.println("Updated SAML provider: " + saml.getProviderId());

取得供應器

以下程式碼示範如何使用供應者 ID 擷取特定租用戶的供應者設定:

Node.js

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

Python

saml = tennat_client.get_saml_provider_config('saml.myProvider')
print(saml.display_name, saml.enabled)

Java

SamlProviderConfig saml = tenantAuth.getSamlProviderConfig("saml.myProvider");

// Get display name and whether it is enabled.
System.out.println(saml.getDisplayName() + " " + saml.isEnabled());

商店資訊供應商

以下程式碼說明如何列出特定租用戶的供應器設定:

Node.js

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

Python

for saml in tenant_client.list_saml_provider_configs('nextPageToken').iterate_all():
    print(saml.provider_id)

Java

ListProviderConfigsPage<SamlProviderConfig> page = tenantAuth.listSamlProviderConfigs(
    "nextPageToken");
for (SamlProviderConfig saml : page.iterateAll()) {
  System.out.println(saml.getProviderId());
}

刪除供應者

以下程式碼說明如何刪除提供者:

Node.js

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

Python

tenant_client.delete_saml_provider_config('saml.myProvider')

Java

tenantAuth.deleteSamlProviderConfig("saml.myProvider");

OIDC 提供者的管理方式與專案層級 OIDC 提供者類似,但可透過對應的 TenantAwareAuth 執行個體管理,而非 Auth 專案層級執行個體。

如需更多資訊,請參閱「透過程式管理 SAML 和 OIDC 供應商」。

管理租用戶專屬使用者

您可以使用 Admin SDK 建立、擷取、更新、刪除及列出特定租用戶的所有使用者。

首先,您需要對應租用戶的 TenantAwareAuth 執行個體:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

取得使用者

您可以使用 uid ID 擷取特定租用戶使用者:

Node.js

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

Python

	# Get an auth.Client from tenant_mgt.auth_for_tenant()
    user = tenant_client.get_user(uid)
    print('Successfully fetched user data:', user.uid)

Java

// Get an auth client from the firebase.App
UserRecord user = tenantAuth.getUser(uid);
System.out.println("Successfully fetched user data: " + user.getDisplayName());

您也可以透過電子郵件地址識別使用者:

Node.js

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

Python

user = tenant_client.get_user_by_email(email)
print('Successfully fetched user data:', user.uid)

Java

// Get an auth client from the firebase.App
UserRecord user = tenantAuth.getUserByEmail(email);
System.out.println("Successfully fetched user data: " + user.getDisplayName());

建立使用者

使用 createUser() 方法為特定租用戶建立新使用者。建立新使用者時,提供 uid 屬於選用項目;如果未指定,Identity Platform 會佈建專屬的 uid

Node.js

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

Python

user = tenant_client.create_user(
    email='user@example.com',
    email_verified=False,
    phone_number='+15555550100',
    password='secretPassword',
    display_name='John Doe',
    photo_url='http://www.example.com/12345678/photo.png',
    disabled=False)
print('Sucessfully created new user:', user.uid)

Java

UserRecord.CreateRequest request = new UserRecord.CreateRequest()
    .setEmail("user@example.com")
    .setEmailVerified(false)
    .setPhoneNumber("+15555550100")
    .setPassword("secretPassword")
    .setDisplayName("John Doe")
    .setPhotoUrl("http://www.example.com/12345678/photo.png")
    .setDisabled(false);
UserRecord user = tenantAuth.createUser(request);
System.out.println("Successfully created user: " + user.getDisplayName());

修改使用者

您可以將現有使用者的 uid 指定給 updateUser() 方法,藉此修改現有使用者:

Node.js

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

Python

user = tenant_client.update_user(
    uid,
    email='user@example.com',
    phone_number='+15555550100',
    email_verified=True,
    password='newPassword',
    display_name='John Doe',
    photo_url='http://www.example.com/12345678/photo.png',
    disabled=True)
print('Sucessfully updated user:', user.uid)

Java

UserRecord.UpdateRequest request = new UserRecord.UpdateRequest(uid)
    .setEmail("user@example.com")
    .setEmailVerified(true)
    .setPhoneNumber("+15555550100")
    .setPassword("newPassword")
    .setDisplayName("John Doe")
    .setPhotoUrl("http://www.example.com/12345678/photo.png")
    .setDisabled(true);
UserRecord user = tenantAuth.updateUser(request);
System.out.println("Successfully updated user: " + user.getDisplayName());

刪除使用者

以下範例說明如何根據使用者的 uid 刪除使用者:

Node.js

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

Python

tenant_client.delete_user(uid)
print('Successfully deleted user')

Java

tenantAuth.deleteUser(uid);

System.out.println("Successfully deleted user: " + uid);

列出使用者

如要以批次方式擷取特定租用戶的完整使用者清單,請使用 listUsers() 方法。每個批次都會包含使用者記錄清單,以及 (如果還有其他使用者) 後續頁面權杖。

Node.js

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

Python

	# Note, behind the scenes, the iterator will retrive 1000 users at a time through the API
    for user in tenant_client.list_users().iterate_all():
        print('User: ' + user.uid)

	# Iterating by pages of 1000 users at a time.
    page = tenant_client.list_users()
    while page:
        for user in page.users:
            print('User: ' + user.uid)
        # Get next batch of users.
        page = page.get_next_page()

Java

// Note, behind the scenes, the ListUsersPage retrieves 1000 Users at a time
// through the API
ListUsersPage  page = tenantAuth.listUsers(null);
for (ExportedUserRecord user : page.iterateAll()) {
  System.out.println("User: " + user.getUid());
}

// Iterating by pages 100 users at a time.
page = tenantAuth.listUsers(null, 100);
while (page != null) {
  for (ExportedUserRecord user : page.getValues()) {
    System.out.println("User: " + user.getUid());
  }

  page = page.getNextPage();
}

如要進一步瞭解,請參閱 Admin SDK 說明文件,瞭解如何管理使用者

匯入使用者

您可以使用 Admin SDK,將使用者大量匯入具有較高權限的特定租用戶。這項功能帶來許多好處,例如可遷移使用者,從其他 Identity Platform 產品、其他租用戶或使用不同雜湊演算法的外部驗證系統。您也可以直接匯入使用者、聯盟供應商 (例如 SAML 和 OIDC) 和自訂宣稱。

首先,請為對應的租用戶取得 TenantAwareAuth 例項:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

您可以使用特定的雜湊演算法,一次匯入最多 1,000 位使用者。

Node.js

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

Python

users = [
    auth.ImportUserRecord(
        uid='uid1',
        email='user1@example.com',
        password_hash=b'password_hash_1',
        password_salt=b'salt1'
    ),
    auth.ImportUserRecord(
        uid='uid2',
        email='user2@example.com',
        password_hash=b'password_hash_2',
        password_salt=b'salt2'
    ),
]

hash_alg = auth.UserImportHash.hmac_sha256(key=b'secret')
try:
    result = tenant_client.import_users(users, hash_alg=hash_alg)
    for err in result.errors:
        print('Failed to import user:', err.reason)
except exceptions.FirebaseError as error:
    print('Error importing users:', error)

Java

List<ImportUserRecord> users = new ArrayList<>();
users.add(ImportUserRecord.builder()
    .setUid("uid1")
    .setEmail("user1@example.com")
    .setPasswordHash("password-hash-1".getBytes())
    .setPasswordSalt("salt1".getBytes())
    .build());
users.add(ImportUserRecord.builder()
    .setUid("uid2")
    .setEmail("user2@example.com")
    .setPasswordHash("password-hash-2".getBytes())
    .setPasswordSalt("salt2".getBytes())
    .build());
UserImportHash hmacSha256 = HmacSha256.builder()
    .setKey("secret".getBytes())
    .build();
UserImportResult result = tenantAuth.importUsers(users, UserImportOptions.withHash(hmacSha256));

for (ErrorInfo error : result.getErrors()) {
  System.out.println("Failed to import user: " + error.getReason());
}

所有匯入的使用者都會將 tenantId 設為 tenantAuth.tenantId

您也可以將沒有密碼的使用者匯入特定租用戶。這些使用者可透過聯合提供者和自訂宣稱匯入。

Node.js

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

Python

users = [
    auth.ImportUserRecord(
        uid='some-uid',
        display_name='John Doe',
        email='johndoe@gmail.com',
        photo_url='http://www.example.com/12345678/photo.png',
        email_verified=True,
        phone_number='+11234567890',
        custom_claims={'admin': True}, # set this user as admin
        provider_data=[ # user with SAML provider
            auth.UserProvider(
                uid='saml-uid',
                email='johndoe@gmail.com',
                display_name='John Doe',
                photo_url='http://www.example.com/12345678/photo.png',
                provider_id='saml.acme'
            )
        ],
    ),
]
try:
    result = tenant_client.import_users(users)
    for err in result.errors:
        print('Failed to import user:', err.reason)
except exceptions.FirebaseError as error:
    print('Error importing users:', error)

Java

List<ImportUserRecord> users = new ArrayList<>();
users.add(ImportUserRecord.builder()
    .setUid("some-uid")
    .setDisplayName("John Doe")
    .setEmail("johndoe@acme.com")
    .setPhotoUrl("https://www.example.com/12345678/photo.png")
    .setEmailVerified(true)
    .setPhoneNumber("+11234567890")
    // Set this user as admin.
    .putCustomClaim("admin", true)
    // User with SAML provider.
    .addUserProvider(UserProvider.builder()
        .setUid("saml-uid")
        .setEmail("johndoe@acme.com")
        .setDisplayName("John Doe")
        .setPhotoUrl("https://www.example.com/12345678/photo.png")
        .setProviderId("saml.acme")
        .build())
    .build());

UserImportResult result = tenantAuth.importUsers(users);

for (ErrorInfo error : result.getErrors()) {
  System.out.println("Failed to import user: " + error.getReason());
}

如需瞭解詳情,請參閱 Admin SDK 說明文件中的「匯入使用者」。

身分驗證

當 Identity Platform 用戶端應用程式與自訂後端伺服器通訊時,需要在該伺服器上識別目前登入的使用者。只要在使用者成功登入後,透過安全連線傳送至伺服器的使用者 ID 權杖,即可安全地完成這項操作。接著,伺服器就能驗證 ID 權杖的完整性和真實性。

Admin SDK 內建驗證及解碼特定租用戶 ID 權杖的方法。

使用者透過用戶端成功登入特定租用戶後,請使用用戶端 SDK 擷取使用者的 ID 權杖:

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

在伺服器上建立 TenantAwareAuth 執行個體:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

接著,您可以驗證該特定租戶的 ID 權杖:

Node.js

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

伺服器端資源可能會開放給多個租用戶存取,且存取權限各有不同。在這種情況下,您可能無法事先得知租用戶 ID,因此可以先在專案層級驗證 ID 權杖。

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

Python

	# id_token comes from the client app
    try:
        decoded_token = tenant_client.verify_id_token(id_token)

        # This should be set to TENANT-ID. Otherwise TenantIdMismatchError error raised.
        print('Verified ID token from tenant:', decoded_token['firebase']['tenant'])
    except tenant_mgt.TenantIdMismatchError:
        # Token revoked, inform the user to reauthenticate or signOut().
        pass

Java

try {
  // idToken comes from the client app
  FirebaseToken token = tenantAuth.verifyIdToken(idToken);
  // TenantId on the FirebaseToken should be set to TENANT-ID.
  // Otherwise "tenant-id-mismatch" error thrown.
  System.out.println("Verified ID token from tenant: " + token.getTenantId());
} catch (FirebaseAuthException e) {
  System.out.println("error verifying ID token: " + e.getMessage());
}

如要進一步瞭解如何驗證 ID 權杖,請參閱「管理員 SDK」說明文件。

管理使用者工作階段

Identity Platform 工作階段的生命週期很長。每次使用者登入時,系統會在 Identity Platform 伺服器上驗證使用者的憑證,然後交換短期 ID 權杖和長期更新權杖。ID 權杖的有效時間為 1 小時。除非使用者遭到停用、刪除,或帳戶發生重大變更 (例如電子郵件或密碼更新),否則重新整理權杖永遠不會過期。

在某些情況下,基於安全性考量,可能需要撤銷使用者的更新權杖,例如使用者回報裝置遺失或遭竊、發現應用程式中的一般安全漏洞,或是大量洩漏有效權杖。Admin SDK 提供 API,可針對特定租用戶的指定使用者,撤銷所有核發的重新整理權杖。

首先,您需要一個 TenantAwareAuth 例項:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

接著,您可以指定該使用者的 uid,藉此撤銷重新整理權杖:

Node.js

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

Python

	# 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.
    tenant_client.revoke_refresh_tokens(uid)

    user = tenant_client.get_user(uid)
    # Convert to seconds as the auth_time in the token claims is in seconds.
    revocation_second = user.tokens_valid_after_timestamp / 1000
    print('Tokens revoked at: {0}'.format(revocation_second))

Java

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

// accessing the user's TokenValidAfter
UserRecord user = tenantAuth.getUser(uid);


long timestamp = user.getTokensValidAfterTimestamp() / 1000;
System.out.println("the refresh tokens were revoked at: " + timestamp + " (UTC seconds)");

更新權杖遭到撤銷後,除非使用者重新驗證,否則無法為該使用者核發新的 ID 權杖。不過,現有的 ID 權杖會在自然到期時間 (一小時) 前保持有效。

您可以指定選用的 checkRevoked 參數,驗證未過期的有效 ID 權杖是否遭到撤銷。這項作業會在驗證權杖的完整性和真實性後,檢查權杖是否遭到撤銷。

Node.js

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

Python

	# Verify the ID token for a specific tenant while checking if the token is revoked.
    try:
        # Verify the ID token while checking if the token is revoked by
        # passing check_revoked=True.
        decoded_token = tenant_client.verify_id_token(id_token, check_revoked=True)
        # Token is valid and not revoked.
        uid = decoded_token['uid']
    except tenant_mgt.TenantIdMismatchError:
        # Token belongs to a different tenant.
        pass
    except auth.RevokedIdTokenError:
        # Token revoked, inform the user to reauthenticate or signOut().
        pass
    except auth.UserDisabledError:
        # Token belongs to a disabled user record.
        pass
    except auth.InvalidIdTokenError:
        # Token is invalid
        pass

Java

// Verify the ID token for a specific tenant while checking if the token is revoked.
boolean checkRevoked = true;
try {
  FirebaseToken token = tenantAuth.verifyIdToken(idToken, checkRevoked);
  System.out.println("Verified ID token for: " + token.getUid());
} catch (FirebaseAuthException e) {
  if ("id-token-revoked".equals(e.getErrorCode())) {
    // Token is revoked. Inform the user to re-authenticate or signOut() the user.
  } else {
    // Token is invalid
  }
}

如要進一步瞭解,請參閱 Admin SDK 說明文件中的「管理工作階段」一節。

使用自訂宣告控管存取權

Admin SDK 可支援針對特定租用戶的使用者帳戶定義自訂屬性。這些屬性可讓您實作不同的存取權控管策略,例如角色式存取權控管。這些屬性可用來為使用者提供不同層級的存取權,並由應用程式的安全性規則強制執行。

首先,請為對應的租用戶取得 TenantAwareAuth 例項:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

自訂權利聲明可能包含敏感資料,因此應僅透過使用 Admin SDK 的權限伺服器環境設定。

Node.js

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

Python

# Set admin privilege on the user corresponding to uid.
tenant_client.set_custom_user_claims(uid, {'admin': True})
# The new custom claims will propagate to the user's ID token the
# next time a new one is issued.

Java

// Set admin privilege on the user corresponding to uid in a specific tenant.
Map<String, Object> claims = new HashMap<>();
claims.put("admin", true);
tenantAuth.setCustomUserClaims(uid, claims);
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.

下次使用者在現有工作階段登入或重新整理 ID 權杖時,新設定的自訂屬性會顯示在權杖酬載的頂層屬性中。在前述範例中,ID 權杖包含額外的宣告:{admin: true}

驗證 ID 權杖並解碼其酬載後,即可檢查額外的自訂宣告,以便強制執行存取控制。

Node.js

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

Python

# Verify the ID token first.
claims = tenant_client.verify_id_token(id_token)
if claims['admin'] is True:
    # Allow access to requested admin resource.
    pass

Java

// Verify the ID token first.
FirebaseToken token = tenantAuth.verifyIdToken(idToken);
if (Boolean.TRUE.equals(token.getClaims().get("admin"))) {
  //Allow access to requested admin resource.
}
// Verify the ID token first.
FirebaseToken decoded = tenantAuth.verifyIdToken(idToken);
if (Boolean.TRUE.equals(decoded.getClaims().get("admin"))) {
  // Allow access to requested admin resource.
}

特定租用戶現有使用者的自訂憑證附加資訊,也可做為使用者記錄的屬性。

Node.js

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

Python

	# Lookup the user associated with the specified uid.
    user = tenant_client.get_user(uid)

	# The claims can be accessed on the user record.
    print(user.custom_claims.get('admin'))

Java

// Lookup the user associated with the specified uid in a specific tenant.
UserRecord user = tenantAuth.getUser(uid);
System.out.println(user.getCustomClaims().get("admin"));

如要進一步瞭解,請參閱 Admin SDK 說明文件中的「自訂宣告」一節。

您可以使用 Identity Platform 用戶端 SDK,向特定租用戶的使用者傳送電子郵件,內含可用於重設密碼、驗證電子郵件地址和以電子郵件登入的連結。這些電子郵件由 Google 寄送,可自訂的內容有限。

您可以使用 Admin SDK,透過程式輔助方式在特定租用戶的範圍內產生這些連結。

首先,請為對應的租用戶取得 TenantAwareAuth 例項:

Node.js

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

Python

tenant_client = tenant_mgt.auth_for_tenant('TENANT-ID')

Java

TenantAwareFirebaseAuth tenantAuth = FirebaseAuth.getInstance().getTenantManager()
    .getAuthForTenant("TENANT-ID");

以下範例說明如何產生連結,以便驗證指定租用戶的使用者電子郵件地址:

Node.js

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

Python

action_code_settings = auth.ActionCodeSettings(
    url='https://www.example.com/checkout?cartId=1234',
    handle_code_in_app=True,
    ios_bundle_id='com.example.ios',
    android_package_name='com.example.android',
    android_install_app=True,
    android_minimum_version='12',
    # FDL custom domain.
    dynamic_link_domain='coolapp.page.link',
)

email = 'user@example.com'
link = tenant_client.generate_email_verification_link(email, action_code_settings)
# Construct email from a template embedding the link, and send
# using a custom SMTP server.
send_custom_email(email, link)

Java

ActionCodeSettings actionCodeSettings = ActionCodeSettings.builder()
    // URL you want to redirect back to. The domain (www.example.com) for
    // this URL must be whitelisted in the GCP Console.
    .setUrl("https://www.example.com/checkout?cartId=1234")
    // This must be true for email link sign-in.
    .setHandleCodeInApp(true)
    .setIosBundleId("com.example.ios")
    .setAndroidPackageName("com.example.android")
    .setAndroidInstallApp(true)
    .setAndroidMinimumVersion("12")
    // FDL custom domain.
    .setDynamicLinkDomain("coolapp.page.link")
    .build();

String link = tenantAuth.generateEmailVerificationLink(email, actionCodeSettings);

// Construct email verification template, embed the link and send
// using custom SMTP server.
sendCustomEmail(email, displayName, link);

您也可以使用類似機制產生密碼重設連結和以電子郵件為基礎的登入連結。請注意,在租用戶內容中產生電子郵件動作連結時,必須先從連結剖析租用戶 ID,並在用戶端 Auth 例項上設定,才能套用程式碼。

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

詳情請參閱「Admin SDK 說明文件」中的「電子郵件動作連結」。

錯誤訊息

下表列出您可能會遇到的常見錯誤訊息。

錯誤代碼 說明和解決步驟
auth/billing-not-enabled 必須啟用結算功能,才能使用這項功能。
auth/invalid-display-name displayName 欄位必須是有效的字串。
auth/invalid-name 您提供的資源名稱無效。
auth/invalid-page-token 分頁符記必須是有效的非空白字串。
auth/invalid-project-id 父項專案無效。父項專案未啟用或已停用多租用戶功能。
auth/invalid-tenant-id 租戶 ID 必須是有效的非空白字串。
auth/mismatching-tenant-id 使用者租用戶 ID 與目前的 TenantAwareAuth 租用戶 ID 不符。
auth/missing-display-name 正在建立或編輯的資源缺少有效的顯示名稱。
auth/insufficient-permission 使用者權限不足,無法存取要求的資源或執行特定租用戶作業。
auth/quota-exceeded 已超出指定作業的專案配額。
auth/tenant-not-found 沒有與提供的 ID 對應的租用戶。
auth/unsupported-tenant-operation 多租戶環境不支援這項作業。
auth/invalid-testing-phone-number 提供的測試用電話號碼或測試代碼無效。
auth/test-phone-number-limit-exceeded 測試電話號碼和驗證碼組合的數量已超過上限。