En este artículo se explica cómo crear tu propia página de autenticación con identidades externas e IAP. Si creas esta página tú mismo, tendrás pleno control sobre el flujo de autenticación y la experiencia de usuario.
Si no necesitas personalizar completamente tu interfaz de usuario, puedes dejar que IAP aloje una página de inicio de sesión por ti o usar FirebaseUI para disfrutar de una experiencia más optimizada.
Información general
Para crear tu propia página de autenticación, sigue estos pasos:
- Habilita las identidades externas. Selecciona Proporcionaré mi propia opción de interfaz de usuario durante la configuración.
- Instala la biblioteca
gcip-iap
. - Configura la interfaz de usuario implementando la interfaz
AuthenticationHandler
. Tu página de autenticación debe gestionar las siguientes situaciones:- Selección de inquilinos
- Autorización de usuario
- Inicio de sesión de usuario
- Gestión de errores
- Opcional: Personaliza tu página de autenticación con funciones adicionales, como barras de progreso, páginas de cierre de sesión y procesamiento de usuarios.
- Prueba tu interfaz de usuario.
Instalar la biblioteca gcip-iap
Para instalar la biblioteca gcip-iap
, ejecuta el siguiente comando:
npm install gcip-iap --save
El módulo gcip-iap
NPM abstrae las comunicaciones entre tu aplicación, las compras en la aplicación y Identity Platform. De esta forma, puedes personalizar todo el flujo de autenticación sin tener que gestionar los intercambios subyacentes entre la interfaz de usuario y las compras en la aplicación.
Usa las importaciones correctas para tu versión del SDK:
gcip-iap v0.1.4 o versiones anteriores
// Import Firebase/GCIP dependencies. These are installed on npm install.
import * as firebase from 'firebase/app';
import 'firebase/auth';
// Import GCIP/IAP module.
import * as ciap from 'gcip-iap';
gcip-iap v1.0.0 a v1.1.0
A partir de la versión v1.0.0, gcip-iap
requiere la dependencia de elemento del mismo nivel firebase
v9 o una versión posterior.
Si vas a migrar a la versión 1.0.0 de gcip-iap
o a una posterior, completa las siguientes acciones:
- Actualiza la versión de
firebase
en tu archivopackage.json
a la 9.6.0 o una posterior. - Actualiza las instrucciones de importación de
firebase
de la siguiente manera:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';
No es necesario realizar cambios adicionales en el código.
gcip-iap v2.0.0
A partir de la versión 2.0.0, gcip-iap
requiere que se vuelva a escribir tu aplicación de interfaz de usuario personalizada con el formato de SDK modular. Si vas a migrar a gcip-iap
v2.0.0 o una versión posterior, sigue estos pasos:
- Actualiza la versión de
firebase
en tu archivopackage.json
a la versión 9.8.3 o posterior. - Actualiza las instrucciones de importación de
firebase
de la siguiente manera:
// Import Firebase modules.
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider } 'firebase/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';
Configurar la interfaz de usuario
Para configurar la interfaz de usuario, crea una clase personalizada que implemente la interfaz AuthenticationHandler
:
interface AuthenticationHandler {
languageCode?: string | null;
getAuth(apiKey: string, tenantId: string | null): FirebaseAuth;
startSignIn(auth: FirebaseAuth, match?: SelectedTenantInfo): Promise<UserCredential>;
selectTenant?(projectConfig: ProjectConfig, tenantIds: string[]): Promise<SelectedTenantInfo>;
completeSignOut(): Promise<void>;
processUser?(user: User): Promise<User>;
showProgressBar?(): void;
hideProgressBar?(): void;
handleError?(error: Error | CIAPError): void;
}
Durante la autenticación, la biblioteca llama automáticamente a los métodos de AuthenticationHandler
.
Seleccionar clientes
Para seleccionar un arrendatario, implementa selectTenant()
. Puedes implementar este método para elegir un arrendatario mediante programación o mostrar una interfaz de usuario para que el usuario pueda seleccionar uno.
En ambos casos, la biblioteca usa el objeto SelectedTenantInfo
devuelto para completar el flujo de autenticación. Contiene el ID del arrendatario seleccionado, los IDs de los proveedores y el correo que ha introducido el usuario.
Si tienes varios inquilinos en tu proyecto, debes seleccionar uno antes de poder autenticar a un usuario. Si solo tienes un inquilino o usas la autenticación a nivel de proyecto, no tienes que implementar selectTenant()
.
IAP admite los mismos proveedores que Identity Platform, como los siguientes:
- Correo y contraseña
- OAuth (Google, Facebook, Twitter, GitHub, Microsoft, etc.)
- SAML
- OIDC
- Número de teléfono
- Personalizado
- Anónimo
No se admiten los tipos de autenticación de número de teléfono, personalizada y anónima para la arquitectura multiempresa.
Seleccionar propietarios de forma programática
Para seleccionar un arrendatario de forma programática, aprovecha el contexto actual. La clase Authentication
contiene getOriginalURL()
, que devuelve la URL a la que accedía el usuario antes de la autenticación.
Úsalo para buscar una coincidencia en una lista de clientes asociados:
// Select provider programmatically.
selectTenant(projectConfig, tenantIds) {
return new Promise((resolve, reject) => {
// Show UI to select the tenant.
auth.getOriginalURL()
.then((originalUrl) => {
resolve({
tenantId: getMatchingTenantBasedOnVisitedUrl(originalUrl),
// If associated provider IDs can also be determined,
// populate this list.
providerIds: [],
});
})
.catch(reject);
});
}
Permitir que los usuarios seleccionen clientes
Para permitir que el usuario seleccione un arrendatario, muestra una lista de arrendatarios y pide al usuario que elija uno o que introduzca su dirección de correo electrónico y, a continuación, busca una coincidencia basada en el dominio:
// Select provider by showing UI.
selectTenant(projectConfig, tenantIds) {
return new Promise((resolve, reject) => {
// Show UI to select the tenant.
renderSelectTenant(
tenantIds,
// On tenant selection.
(selectedTenantId) => {
resolve({
tenantId: selectedTenantId,
// If associated provider IDs can also be determined,
// populate this list.
providerIds: [],
// If email is available, populate this field too.
email: undefined,
});
});
});
}
Autenticar usuarios
Una vez que tengas un proveedor, implementa getAuth()
para devolver una instancia de Auth, correspondiente a la clave de API y al ID de arrendatario proporcionados. Si no se proporciona ningún ID de cliente, se usan los proveedores de identidades a nivel de proyecto.
getAuth()
registra dónde se almacena el usuario correspondiente a la configuración proporcionada. También permite actualizar de forma silenciosa el token de ID de Identity Platform de un usuario autenticado previamente sin que tenga que volver a introducir sus credenciales.
Si usas varios recursos de IAP con diferentes arrendatarios, te recomendamos que uses una instancia de autenticación única para cada recurso. De esta forma, varios recursos con configuraciones diferentes pueden usar la misma página de autenticación. También permite que varios usuarios inicien sesión al mismo tiempo sin cerrar la sesión del usuario anterior.
A continuación, se muestra un ejemplo de cómo implementar getAuth()
:
gcip-iap v1.0.0
getAuth(apiKey, tenantId) {
let auth = null;
// Make sure the expected API key is being used.
if (apiKey !== expectedApiKey) {
throw new Error('Invalid project!');
}
try {
auth = firebase.app(tenantId || undefined).auth();
// Tenant ID should be already set on initialization below.
} catch (e) {
// Use different App names for every tenant so that
// multiple users can be signed in at the same time (one per tenant).
const app = firebase.initializeApp(this.config, tenantId || '[DEFAULT]');
auth = app.auth();
// Set the tenant ID on the Auth instance.
auth.tenantId = tenantId || null;
}
return auth;
}
gcip-iap v2.0.0
import {initializeApp, getApp} from 'firebase/app';
import {getAuth} from 'firebase/auth';
getAuth(apiKey, tenantId) {
let auth = null;
// Make sure the expected API key is being used.
if (apiKey !== expectedApiKey) {
throw new Error('Invalid project!');
}
try {
auth = getAuth(getApp(tenantId || undefined));
// Tenant ID should be already set on initialization below.
} catch (e) {
// Use different App names for every tenant so that
// multiple users can be signed in at the same time (one per tenant).
const app = initializeApp(this.config, tenantId || '[DEFAULT]');
auth = getAuth(app);
// Set the tenant ID on the Auth instance.
auth.tenantId = tenantId || null;
}
return auth;
}
Inicio de sesión de usuarios
Para gestionar el inicio de sesión, implementa startSignIn()
, muestra una interfaz de usuario para que el usuario se autentique y, a continuación, devuelve un UserCredential
para el usuario que ha iniciado sesión cuando se complete el proceso.
En un entorno multiempresa, puedes determinar los métodos de autenticación disponibles a partir de SelectedTenantInfo
, si se ha proporcionado. Esta variable contiene la misma información que devuelve selectTenant()
.
En el siguiente ejemplo se muestra una implementación de startSignIn()
para un usuario que ya tiene una dirección de correo y una contraseña:
gcip-iap v1.0.0
startSignIn(auth, selectedTenantInfo) {
return new Promise((resolve, reject) => {
// Show the UI to sign-in or sign-up a user.
$('#sign-in-form').on('submit', (e) => {
const email = $('#email').val();
const password = $('#password').val();
// Example: Ask the user for an email and password.
// Note: The method of sign in may have already been determined from the
// selectedTenantInfo object.
auth.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
resolve(userCredential);
})
.catch((error) => {
// Show the error message.
});
});
});
}
gcip-iap v2.0.0
import {signInWithEmailAndPassword} from 'firebase/auth';
startSignIn(auth, selectedTenantInfo) {
return new Promise((resolve, reject) => {
// Show the UI to sign-in or sign-up a user.
$('#sign-in-form').on('submit', (e) => {
const email = $('#email').val();
const password = $('#password').val();
// Example: Ask the user for an email and password.
// Note: The method of sign in may have already been determined from the
// selectedTenantInfo object.
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
resolve(userCredential);
})
.catch((error) => {
// Show the error message.
});
});
});
}
También puedes iniciar la sesión de los usuarios con un proveedor federado, como SAML u OIDC, mediante una ventana emergente o una redirección:
gcip-iap v1.0.0
startSignIn(auth, selectedTenantInfo) {
// Show the UI to sign-in or sign-up a user.
return new Promise((resolve, reject) => {
// Provide the user multiple buttons to sign-in.
// For example sign-in with popup using a SAML provider.
// Note: The method of sign in may have already been determined from the
// selectedTenantInfo object.
const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');
auth.signInWithPopup(provider)
.then((userCredential) => {
resolve(userCredential);
})
.catch((error) => {
// Show the error message.
});
// Using redirect flow. When the page redirects back and sign-in completes,
// ciap will detect the result and complete sign-in without any additional
// action.
auth.signInWithRedirect(provider);
});
}
gcip-iap v2.0.0
import {signInWithPopup, SAMLAuthProvider} from 'firebase/auth';
startSignIn(auth, selectedTenantInfo) {
// Show the UI to sign-in or sign-up a user.
return new Promise((resolve, reject) => {
// Provide the user multiple buttons to sign-in.
// For example sign-in with popup using a SAML provider.
// Note: The method of sign in might have already been determined from the
// selectedTenantInfo object.
const provider = new SAMLAuthProvider('saml.myProvider');
signInWithPopup(auth, provider)
.then((userCredential) => {
resolve(userCredential);
})
.catch((error) => {
// Show the error message.
});
// Using redirect flow. When the page redirects back and sign-in completes,
// ciap will detect the result and complete sign-in without any additional
// action.
signInWithRedirect(auth, provider);
});
}
Algunos proveedores de OAuth admiten el envío de una sugerencia de inicio de sesión para iniciar sesión:
gcip-iap v1.0.0
startSignIn(auth, selectedTenantInfo) {
// Show the UI to sign-in or sign-up a user.
return new Promise((resolve, reject) => {
// Use selectedTenantInfo to determine the provider and pass the login hint
// if that provider supports it and the user specified an email address.
if (selectedTenantInfo &&
selectedTenantInfo.providerIds &&
selectedTenantInfo.providerIds.indexOf('microsoft.com') !== -1) {
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.setCustomParameters({
login_hint: selectedTenantInfo.email || undefined,
});
} else {
// Figure out the provider used...
}
auth.signInWithPopup(provider)
.then((userCredential) => {
resolve(userCredential);
})
.catch((error) => {
// Show the error message.
});
});
}
gcip-iap v2.0.0
import {signInWithPopup, OAuthProvider} from 'firebase/auth';
startSignIn(auth, selectedTenantInfo) {
// Show the UI to sign in or sign up a user.
return new Promise((resolve, reject) => {
// Use selectedTenantInfo to determine the provider and pass the login hint
// if that provider supports it and the user specified an email address.
if (selectedTenantInfo &&
selectedTenantInfo.providerIds &&
selectedTenantInfo.providerIds.indexOf('microsoft.com') !== -1) {
const provider = new OAuthProvider('microsoft.com');
provider.setCustomParameters({
login_hint: selectedTenantInfo.email || undefined,
});
} else {
// Figure out the provider used...
}
signInWithPopup(auth, provider)
.then((userCredential) => {
resolve(userCredential);
})
.catch((error) => {
// Show the error message.
});
});
}
Consulta más información sobre la autenticación con multitenencia.
Gestionar errores
Para mostrar mensajes de error a los usuarios o intentar recuperarse de errores como los tiempos de espera de la red, implementa handleError()
.
En el siguiente ejemplo se implementa handleError()
:
handleError(error) {
showAlert({
code: error.code,
message: error.message,
// Whether to show the retry button. This is only available if the error is
// recoverable via retrial.
retry: !!error.retry,
});
// When user clicks retry, call error.retry();
$('.alert-link').on('click', (e) => {
error.retry();
e.preventDefault();
return false;
});
}
En la tabla que se muestra más abajo se enumeran los códigos de error específicos de las compras en aplicaciones que se pueden devolver. Identity Platform también puede devolver errores. Consulta la documentación de firebase.auth.Auth
.
Código de error | Descripción |
---|---|
invalid-argument |
El cliente especificó un argumento que no es válido. |
failed-precondition |
No se puede realizar la solicitud debido al estado actual del sistema. |
out-of-range |
El cliente especificó un intervalo que no es válido. |
unauthenticated |
La solicitud no se ha autenticado porque el token de OAuth ha caducado, no es válido o no se ha encontrado. |
permission-denied |
El cliente no tiene suficientes permisos o la interfaz de usuario está alojada en un dominio no autorizado. |
not-found . |
No se ha encontrado el recurso especificado. |
aborted |
Se ha producido un conflicto de simultaneidad (por ejemplo, relacionado con acciones de lectura, modificación y escritura). |
already-exists |
El cliente ha intentado crear un recurso que ya existe. |
resource-exhausted |
Se ha agotado la cuota del recurso o se ha alcanzado el límite de frecuencia. |
cancelled |
El cliente ha cancelado la solicitud. |
data-loss |
Se han perdido o dañado los datos de forma irrecuperable. |
unknown |
Se ha producido un error de servidor desconocido. |
internal |
Error de servidor interno. |
not-implemented |
El servidor no ha implementado el método de API. |
unavailable |
Servicio no disponible. |
restart-process |
Vuelve a visitar la URL que te redirigió a esta página para reiniciar el proceso de autenticación. |
deadline-exceeded |
Se ha superado el plazo límite de la solicitud. |
authentication-uri-fail |
No se ha podido generar el URI de autenticación. |
gcip-token-invalid |
Se ha proporcionado un token de ID de GCIP no válido. |
gcip-redirect-invalid |
URL de redirección no válida. |
get-project-mapping-fail |
No se ha podido obtener el ID del proyecto. |
gcip-id-token-encryption-error |
Error de cifrado del token de ID de GCIP. |
gcip-id-token-decryption-error |
Error al descifrar el token de ID de GCIP. |
gcip-id-token-unescape-error |
No se ha podido descodificar la cadena base64 segura para la Web. |
resource-missing-gcip-sign-in-url |
Falta la URL de autenticación de GCIP del recurso de IAP especificado. |
Personalizar la interfaz de usuario
Puedes personalizar tu página de autenticación con funciones opcionales, como barras de progreso y páginas de cierre de sesión.
Mostrar una interfaz de usuario de progreso
Para mostrar una interfaz de usuario de progreso personalizada al usuario cada vez que el módulo gcip-iap
ejecute tareas de red de larga duración, implementa showProgressBar()
y hideProgressBar()
.
Cerrar la sesión de los usuarios
En algunos casos, puede que quieras permitir que los usuarios cierren sesión en todas las sesiones actuales que compartan la misma URL de autenticación.
Después de que un usuario cierre sesión, puede que no haya ninguna URL a la que redirigirlo.
Esto suele ocurrir cuando un usuario cierra sesión en todos los tenants asociados a una página de inicio de sesión. En este caso, implementa completeSignOut()
para mostrar un mensaje que indique que el usuario ha cerrado sesión correctamente. Si no implementas este método, se mostrará una página en blanco cuando un usuario cierre sesión.
Procesamiento de usuarios
Para modificar un usuario que ha iniciado sesión antes de redirigirlo al recurso de IAP, implementa processUser()
.
Puedes usar este método para hacer lo siguiente:
- Enlace a proveedores adicionales.
- Actualiza el perfil del usuario.
- Pedir datos adicionales al usuario después del registro.
- Procesa los tokens de acceso de OAuth devueltos por
getRedirectResult()
después de llamar asignInWithRedirect()
.
A continuación, se muestra un ejemplo de implementación de processUser()
:
gcip-iap v1.0.0
processUser(user) {
return lastAuthUsed.getRedirectResult().then(function(result) {
// Save additional data, or ask the user for additional profile information
// to store in database, etc.
if (result) {
// Save result.additionalUserInfo.
// Save result.credential.accessToken for OAuth provider, etc.
}
// Return the user.
return user;
});
}
gcip-iap v2.0.0
import {getRedirectResult} from 'firebase/auth';
processUser(user) {
return getRedirectResult(lastAuthUsed).then(function(result) {
// Save additional data, or ask the user for additional profile information
// to store in database, etc.
if (result) {
// Save result.additionalUserInfo.
// Save result.credential.accessToken for OAuth provider, etc.
}
// Return the user.
return user;
});
}
Si quieres que los cambios que hagas en un usuario se reflejen en las reclamaciones del token de ID que IAP propaga a tu aplicación, debes forzar la actualización del token:
gcip-iap v1.0.0
processUser(user) {
return user.updateProfile({
photoURL: 'https://example.com/profile/1234/photo.png',
}).then(function() {
// To reflect updated photoURL in the ID token, force token
// refresh.
return user.getIdToken(true);
}).then(function() {
return user;
});
}
gcip-iap v2.0.0
import {updateProfile} from 'firebase/auth';
processUser(user) {
return updateProfile(user, {
photoURL: 'https://example.com/profile/1234/photo.png',
}).then(function() {
// To reflect updated photoURL in the ID token, force token
// refresh.
return user.getIdToken(true);
}).then(function() {
return user;
});
}
Probar la interfaz de usuario
Una vez que hayas creado una clase que implemente AuthenticationHandler
, podrás usarla para crear una instancia de Authentication
e iniciarla:
// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();
Implementa tu aplicación y ve a la página de autenticación. Deberías ver tu interfaz de inicio de sesión personalizada.
Siguientes pasos
- Consulte cómo acceder a recursos que no son de Google de forma programática.
- Consulta información sobre cómo gestionar sesiones.
- Consulta cómo funcionan las identidades externas con IAP.