Gestionar clientes de Identity Platform de forma programática

En este documento se explica cómo usar el SDK de administrador de Identity Platform para gestionar inquilinos y sus usuarios de forma programática. Estas son algunas de las actividades que puedes realizar como administrador:

  • Gestión de usuarios: crea, actualiza, elimina y muestra usuarios de un tenant específico.

  • Verificación de identidad: identifica a los usuarios de una aplicación para restringir el acceso a los recursos de tu servidor.

  • Importar usuarios: migra usuarios desde un sistema de autenticación externo u otro proyecto o inquilino de Identity Platform.

  • Control de acceso con reclamaciones personalizadas: define atributos personalizados en cuentas de usuario de un inquilino específico e implementa varias estrategias de control de acceso, como el control de acceso basado en roles.

  • Gestión de sesiones de usuario: revoca los tokens de actualización de un usuario de un inquilino específico.

  • Enlaces de acción de correo electrónico: genera enlaces de correo personalizados para restablecer contraseñas, iniciar sesión con un enlace de correo y verificar el correo de los usuarios de un inquilino específico.

  • Gestión de clientes: crea, muestra, obtiene, actualiza y elimina clientes de un proyecto de Identity Platform específico.

  • Gestionar proveedores de OIDC y SAML en inquilinos: gestiona de forma programática las configuraciones de OIDC y SAML en un inquilino específico.

Antes de empezar

Funciones compatibles

En la siguiente tabla se indican las funciones que admite cada SDK en un entorno multiempresa:

Función Node.js Java Python Go C#
Creación de tokens personalizados
Verificar tokens de ID
Gestionar usuarios
Controlar el acceso con reclamaciones personalizadas
Revocar tokens de actualización
Importar usuarios
Generar enlaces de acción de correo
Autenticación multifactor
Gestionar configuraciones de proveedores de SAML y OIDC
Gestión de cookies de sesión

En la siguiente tabla se muestran los métodos de inicio de sesión que puedes configurar con el SDK de administrador y la consola Google Cloud en un contexto específico de un arrendatario:

Función Google Cloud consola SDK de administrador
Correo electrónico
OIDC
SAML
Redes sociales
Teléfono
Autenticación multifactor
Anónimo

Gestión de clientes

Con el SDK de administrador, puedes gestionar los arrendatarios de forma programática desde un entorno de servidor seguro en lugar de usar la consola. Google Cloud Esto incluye la posibilidad de crear, enumerar, obtener, modificar o eliminar inquilinos.

Cada propietario tiene sus propios proveedores de identidad, configuración y conjuntos de usuarios. Las operaciones de gestión de la configuración de los inquilinos (CRUD) están disponibles en la instancia del proyecto principal mediante admin.auth().tenantManager().

Una configuración de arrendatario proporciona información sobre un arrendatario, como su nombre visible, su identificador y su configuración de autenticación de correo electrónico.

El resto de los ajustes de un arrendatario (como los dominios incluidos en la lista blanca y los URIs de redirección autenticados) se heredan del proyecto principal. Deben gestionarse con la consola Google Cloud .

Para operaciones como la gestión de usuarios específicos de un arrendatario, la configuración de proveedores de OIDC o SAML y la generación de enlaces de correo electrónico, necesitarás una instancia de TenantAwareAuth para el arrendatario de destino (identificado por su tenantId único).

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

Todas las llamadas a las APIs de gestión de usuarios, a las APIs de gestión de proveedores de OIDC/SAML y a las APIs de generación de enlaces de correo electrónico se realizarán dentro del ámbito de este cliente (mediante su instancia de TenantAwareAuth).

Obtener un cliente

El SDK de administrador proporciona el método getTenant(), que obtiene información sobre un arrendatario en función de su tenantId (un identificador único del arrendatario).

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

Este método devuelve un objeto Tenant correspondiente al tenantId. Si el tenantId proporcionado no pertenece a ningún arrendatario, la promesa devuelta rechaza la operación y envía un error auth/tenant-not-found.

Ten cuidado de no confundir una instancia de Tenant con un objeto TenantAwareAuth. authInstance.tenantManager().authForTenant() devuelve una instancia de TenantAwareAuth que extiende BaseAuth. La clase Auth también amplía BaseAuth. BaseAuth proporciona APIs para gestionar usuarios y configurar proveedores de OIDC o SAML en diferentes contextos. En el caso de Auth, el contexto es a nivel de proyecto principal. En el caso de TenantAwareAuth, el contexto es a nivel de cliente (el cliente se determina mediante el ID de cliente). El método getTenant() se resolverá con información básica del arrendatario (como el ID, el nombre visible y la configuración del proveedor de correo), pero para llamar a las APIs de ese arrendatario, debes usar authForTenant(tenantFromGetTenant.tenantId).

Crear un cliente

Usa el método createTenant() para crear una configuración de arrendatario:

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

Puedes proporcionar cualquier combinación de estas propiedades:

Propiedad Tipo Descripción
displayName
string
    
Nombre visible del arrendatario. Debe tener entre 4 y 20 caracteres, que pueden ser letras, números y guiones, y debe empezar por una letra.
emailSignInConfig
{
  enable: boolean,
  passwordRequired: boolean
}
    
La configuración del proveedor de inicio de sesión con correo electrónico. Esto incluye si el proveedor de correo está habilitado y si se requiere una contraseña para iniciar sesión con correo. Cuando no sea necesario, se podrá iniciar sesión con contraseña o mediante un enlace enviado por correo electrónico.
multiFactorConfig
{
  state: 'DISABLED' | 'ENABLED',
  factorIds: string[]
}
    
Si la autenticación multifactor está habilitada en el cliente y qué tipos de factores se permiten. Actualmente, el único ID de factor admitido es phone.
testPhoneNumbers
{
  string: string
}
  
Un mapa de números de teléfono y sus códigos de autenticación multifactor asociados para registrarse con fines de prueba. Se permiten un máximo de 10 entradas. Para eliminar todos los números de teléfono de prueba, asigna el valor null a este campo.

El método devuelve un objeto Tenant para el nuevo arrendatario.

Actualizar un cliente

Usa el método updateTenant() para modificar los datos de un arrendatario. Deberás especificar el tenantId, junto con las propiedades que quieras actualizar de ese arrendatario.

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() acepta las mismas propiedades que createTenant(). Todas las propiedades son opcionales. Si no se especifica una propiedad, no se modificará el valor que ya tenga.

El método devuelve un objeto Tenant actualizado al completarse. Si el tenantId proporcionado no pertenece a ningún arrendatario, la promesa devuelta rechaza la operación y muestra un error auth/tenant-not-found.

Eliminar un cliente

Para eliminar un inquilino, puedes usar su 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);

El método devuelve un resultado vacío cuando la eliminación se completa correctamente. Si el tenantId proporcionado no pertenece a ningún arrendatario, la promesa devuelta rechaza la operación y envía un error auth/tenant-not-found.

Mostrar clientes

Usa el método listTenants() para enumerar los inquilinos:

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

Cada lote de resultados contiene una lista de arrendatarios, así como un token de la página siguiente para enumerar el siguiente lote de arrendatarios. Si ya se han enumerado todos los inquilinos, no se devuelve ningún pageToken.

Si no se especifica ningún campo maxResults, el valor predeterminado es 1000 inquilinos por lote. Este es también el número máximo de inquilinos que se pueden incluir en la lista a la vez. Si se introduce un valor superior al máximo, se producirá un error de argumento. Si no se especifica ningún pageToken, el método mostrará los arrendatarios desde el principio.

Gestionar proveedores de SAML y OIDC de forma programática

El SDK de administrador proporciona APIs para gestionar de forma programática las configuraciones de proveedores de lenguaje de marcado para confirmaciones de seguridad (SAML) 2.0 y OpenID Connect (OIDC) desde un entorno de servidor seguro.

Con el SDK de administrador, puedes gestionar estos proveedores en un inquilino concreto. Es similar a gestionar proveedores de OIDC y SAML a nivel de proyecto.

Para gestionar proveedores de un arrendatario, primero debes crear una instancia de 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");

Después, puedes realizar operaciones habituales, como crear, modificar o eliminar proveedores de un arrendatario.

Crear un proveedor

En el siguiente código se muestra cómo crear un proveedor de SAML para un arrendatario:

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

Modificar un proveedor

En el siguiente código se muestra cómo modificar un proveedor:

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

Obtener un proveedor

En el siguiente código se muestra cómo obtener la configuración del proveedor de un inquilino específico mediante su ID de proveedor:

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

Proveedores de fichas

En el siguiente código se muestra cómo enumerar las configuraciones de proveedores de un determinado arrendatario:

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

Eliminar un proveedor

En el siguiente código se muestra cómo eliminar un proveedor:

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

Los proveedores de OIDC se gestionan de forma similar a los proveedores de OIDC a nivel de proyecto, excepto que se pueden gestionar desde la instancia de TenantAwareAuth correspondiente, en lugar de desde una instancia de Auth a nivel de proyecto.

Para obtener más información, consulta el artículo Gestionar proveedores de SAML y OIDC de forma programática.

Gestionar usuarios específicos de un arrendatario

Puedes usar el SDK de administrador para crear, recuperar, actualizar, eliminar y enumerar todos los usuarios de un inquilino específico.

Para empezar, necesitas una instancia de TenantAwareAuth para el arrendatario correspondiente:

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

Obtener un usuario

Puedes obtener un usuario específico de un arrendatario con un identificador uid:

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

También puedes identificar a un usuario por su correo electrónico:

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

Crear un usuario

Usa el método createUser() para crear usuarios en un arrendatario específico. Al crear un usuario, proporcionar un uid es opcional. Si no se especifica, Identity Platform asignará uno único.

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

Modificar un usuario

Puedes modificar usuarios que ya tengas especificando su uid en el método 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());

Eliminar usuarios

En el siguiente ejemplo se muestra cómo eliminar un usuario en función de su 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);

Enumerar usuarios

Para recuperar una lista completa de usuarios de un inquilino específico en lotes, usa el método listUsers(). Cada lote contendrá una lista de registros de usuarios, además de un token de la página siguiente si quedan más usuarios.

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

Consulta la documentación del SDK de administrador sobre gestión de usuarios para obtener más información.

Importar usuarios

Puedes usar el SDK de administrador para importar usuarios en bloque a un inquilino específico con privilegios elevados. Esto ofrece numerosas ventajas, como la posibilidad de migrar usuarios desde otro producto de Identity Platform, desde otro arrendatario o desde un sistema de autenticación externo que utilice un algoritmo de cifrado diferente. También puede importar usuarios con proveedores federados (como SAML y OIDC) y reclamaciones personalizadas directamente en bloque.

Para empezar, obtén una instancia TenantAwareAuth del arrendatario correspondiente:

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

Puedes importar hasta 1000 usuarios a la vez con un algoritmo de cifrado específico.

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

Todos los usuarios importados tendrán el tenantId definido en tenantAuth.tenantId.

También se pueden importar usuarios sin contraseña a un inquilino específico. Estos usuarios se pueden importar con proveedores federados y reclamaciones personalizadas.

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

Para obtener más información, consulta el artículo Importar usuarios de la documentación del SDK de administrador.

Verificación de identidad

Cuando una aplicación cliente de Identity Platform se comunica con un servidor backend personalizado, es necesario identificar al usuario que tiene la sesión iniciada en ese servidor. Para hacerlo de forma segura, envía el token de ID del usuario después de que haya iniciado sesión correctamente mediante una conexión segura a tu servidor. A continuación, el servidor puede verificar la integridad y la autenticidad del token de ID.

El SDK Admin tiene un método integrado para verificar y decodificar tokens de ID de un inquilino específico.

Después de que un usuario haya iniciado sesión correctamente en un inquilino específico desde el cliente, recupera el token de ID del usuario con el SDK del cliente:

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

Crea una instancia de TenantAwareAuth en el servidor:

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

A continuación, puede verificar el token de ID de ese inquilino específico:

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

Varios arrendatarios con diferentes niveles de acceso pueden acceder a un recurso del lado del servidor. Como es posible que el ID de cliente no se conozca de antemano en este caso, el token de ID se puede verificar primero a nivel de proyecto.

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

Consulta la documentación del SDK de administrador sobre cómo verificar tokens de ID para obtener más información.

Gestionar sesiones de usuario

Las sesiones de Identity Platform son de larga duración. Cada vez que un usuario inicia sesión, las credenciales del usuario se verifican en el servidor de Identity Platform y, a continuación, se intercambian por un token de ID de corta duración y un token de actualización de larga duración. Los tokens de ID duran una hora. Los tokens de actualización no caducan nunca, excepto cuando se inhabilita o se elimina a un usuario, o cuando se produce un cambio importante en su cuenta (como una actualización del correo o de la contraseña).

En algunos casos, es posible que sea necesario revocar el token de actualización de un usuario por motivos de seguridad, como cuando el usuario informa de que ha perdido su dispositivo o se lo han robado, cuando se descubre una vulnerabilidad general en una aplicación o cuando se produce una filtración a gran escala de tokens activos. El SDK de administrador proporciona una API para revocar todos los tokens de actualización emitidos para un usuario específico de un cliente concreto.

Para empezar, necesitas una instancia de 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");

Los tokens de actualización se pueden revocar especificando el uid de ese usuario:

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(f'Tokens revoked at: {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)");

Una vez que se hayan revocado los tokens de actualización, no se podrán emitir tokens de ID nuevos para ese usuario hasta que vuelva a autenticarse. Sin embargo, los tokens de ID seguirán activos hasta su hora de vencimiento natural (una hora).

Para verificar que un token de ID válido que no haya caducado no se haya revocado, especifica el parámetro opcional checkRevoked. De esta forma, se comprueba si se ha revocado un token después de verificar su integridad y autenticidad.

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

Para obtener más información, consulta la documentación del SDK de administrador sobre gestión de sesiones.

Controlar el acceso con reclamaciones personalizadas

El SDK de administrador permite definir atributos personalizados en las cuentas de usuario de un inquilino específico. Estos atributos te permiten implementar diferentes estrategias de control de acceso, como el control de acceso basado en roles. Los atributos se pueden usar para dar a los usuarios diferentes niveles de acceso que se aplican mediante las reglas de seguridad de la aplicación.

Para empezar, obtén una instancia de TenantAwareAuth para el cliente correspondiente:

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

Las reclamaciones personalizadas pueden contener datos sensibles, por lo que solo deben definirse desde un entorno de servidor privilegiado mediante el SDK de administrador.

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.

Los atributos personalizados que se definan recientemente aparecerán en los atributos de nivel superior de la carga útil del token la próxima vez que el usuario inicie sesión o actualice sus tokens de ID en una sesión activa. En el ejemplo anterior, el token de ID contiene una reclamación adicional: {admin: true}.

Después de verificar el token de ID y decodificar su carga útil, se pueden comprobar las reclamaciones personalizadas adicionales para aplicar el control de acceso.

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

Las reclamaciones personalizadas de un usuario de un inquilino específico también están disponibles como propiedad en el registro del usuario.

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

Consulta más información sobre las reivindicaciones personalizadas en la documentación del SDK de administrador.

Con los SDKs de cliente de Identity Platform, puedes enviar correos a los usuarios de un inquilino específico con enlaces que pueden usar para restablecer contraseñas, verificar direcciones de correo electrónico e iniciar sesión con correo electrónico. Google envía estos correos y tienen opciones de personalización limitadas.

Con el SDK de administrador, puede generar estos enlaces mediante programación en el ámbito de un inquilino específico.

Para empezar, obtén una instancia de TenantAwareAuth para el cliente correspondiente:

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

En el siguiente ejemplo se muestra cómo generar un enlace para verificar la dirección de correo de un usuario en un inquilino específico:

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

Se pueden usar mecanismos similares para generar enlaces de cambio de contraseña y de inicio de sesión por correo electrónico. Ten en cuenta que, al generar un enlace de acción de correo en un contexto de cliente, el ID de cliente se debe analizar a partir del enlace y definir en la instancia de cliente Auth antes de que se pueda aplicar el código.

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

Consulte la sección Enlaces de acción de correo electrónico de la documentación del SDK de administrador para obtener más información.

Mensajes de error

En la siguiente tabla se muestran los mensajes de error habituales que pueden aparecer.

Código de error Descripción y pasos para resolver el problema
auth/billing-not-enabled Para usar esta función, debes habilitar la facturación.
auth/invalid-display-name El campo displayName debe ser una cadena válida.
auth/invalid-name El nombre de recurso proporcionado no es válido.
auth/invalid-page-token El token de página debe ser una cadena no vacía válida.
auth/invalid-project-id El proyecto principal no es válido. El proyecto principal no tiene habilitada la arquitectura multiempresa o no la tenía habilitada.
auth/invalid-tenant-id El ID de cliente debe ser una cadena válida que no esté vacía.
auth/mismatching-tenant-id El ID de inquilino del usuario no coincide con el ID de inquilino de TenantAwareAuth.
auth/missing-display-name El recurso que se está creando o editando no tiene un nombre visible válido.
auth/insufficient-permission El usuario no tiene permisos suficientes para acceder al recurso solicitado o para ejecutar la operación específica del arrendatario.
auth/quota-exceeded Se ha superado la cuota de proyecto de la operación especificada.
auth/tenant-not-found No hay ningún arrendatario que corresponda al identificador proporcionado.
auth/unsupported-tenant-operation Esta operación no se admite en un contexto multiempresa.
auth/invalid-testing-phone-number Se ha proporcionado un número de teléfono de prueba o un código de prueba no válidos.
auth/test-phone-number-limit-exceeded Se ha superado el número máximo de pares de números de teléfono y códigos de prueba permitidos.