Neste artigo, mostramos como criar sua própria página de autenticação usando identidades externas e o IAP. Ao criar essa página, você tem controle total sobre o fluxo de autenticação e a experiência do usuário.
Se você não precisar personalizar totalmente a UI, deixe o IAP hospedar uma página de login para você ou use a FirebaseUI para uma experiência mais simplificada.
Visão geral
Para criar sua própria página de autenticação, siga estas etapas:
- Ative as identidades externas. Selecione a opção Vou fornecer minha própria UI interface durante a configuração.
- Instale a biblioteca
gcip-iap. - Configure a UI implementando a interface
AuthenticationHandler. Sua página de autenticação precisa processar os seguintes cenários:- Seleção de locatário
- Autorização do usuário
- Login do usuário
- Tratamento de erros
- Opcional: personalize sua página de autenticação com recursos adicionais, como barras de progresso, páginas de encerramento de sessão e processamento de usuários.
- Teste sua interface.
Como instalar a biblioteca gcip-iap
Para instalar a biblioteca gcip-iap, execute o seguinte comando:
npm install gcip-iap --save
O módulo gcip-iap do NPM abstrai as comunicações entre seu aplicativo, o IAP e o Identity Platform. Isso permite personalizar todo o fluxo de autenticação sem precisar gerenciar as trocas entre a UI e a IAP.
Use as importações corretas para sua versão do SDK:
gcip-iap v0.1.4 ou anterior
// 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 da versão v1.0.0, o gcip-iap exige a dependência de mesmo nível firebase v9 ou mais recente.
Se você estiver migrando para o gcip-iap v1.0.0 ou mais recente, conclua as seguintes ações:
- Atualize a versão do
firebaseno arquivopackage.jsonpara v9.6.0 ou mais recente. - Atualize as instruções de importação
firebaseda seguinte maneira:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';
Não é necessário fazer outras mudanças no código.
gcip-iap v2.0.0
A partir da versão v2.0.0, o gcip-iap exige a reescrita do aplicativo UI personalizada usando o formato de SDK modular. Se você estiver migrando para o gcip-iap v2.0.0 ou mais recente, conclua as seguintes ações:
- Atualize a versão do
firebaseno arquivopackage.jsonpara v9.8.3 ou mais recente. - Atualize as instruções de importação
firebaseda seguinte maneira:
// 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 a UI
Para configurar a UI, crie uma classe personalizada que implemente a interface 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 a autenticação, a biblioteca chama automaticamente os métodos de AuthenticationHandler.
Selecionar locatários
Para selecionar um locatário, implemente selectTenant(). Você pode implementar esse método para escolher um locatário de forma programática ou mostrar uma UI para que o usuário possa selecionar um.
Em ambos os casos, a biblioteca usa o objeto SelectedTenantInfo retornado
para concluir o fluxo de autenticação. Ele contém o ID do locatário selecionado, os IDs do provedor e o e-mail que o usuário digitou.
Se você tiver vários locatários no seu projeto, selecione um antes de
autenticar um usuário. Se você tiver apenas um locatário ou estiver usando a autenticação para envolvidos no projeto, não será necessário implementar selectTenant().
O IAP aceita os mesmos provedores do Identity Platform, como:
- E-mail e senha
- OAuth (Google, Facebook, Twitter, GitHub, Microsoft etc.)
- SAML
- OIDC
- Número de telefone
- Personalizado
- Anônimo
Os tipos de autenticação por número de telefone, personalizada e anônima não são compatíveis com a multitenância.
Selecionar locatários de forma programática
Para selecionar um locatário de forma programática, use o contexto atual. A classe
Authentication contém getOriginalURL() que retorna o
URL que o usuário estava acessando antes da autenticação.
Use isso para localizar uma correspondência em uma lista de locatários associados:
// 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 os usuários selecionem locatários
Para permitir que o usuário selecione um locatário, mostre uma lista de locatários e peça para ele escolher um ou insira o endereço de e-mail e encontre uma correspondência com base no domínio:
// 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,
});
});
});
}
Como autenticar usuários
Depois de ter um provedor, implemente getAuth() para retornar uma instância Auth,
correspondente à chave de API e ao ID do locatário fornecidos. Se nenhum ID de locatário for fornecido, use provedores de identidade para envolvidos no projeto.
getAuth() rastreia onde o usuário correspondente à configuração fornecida está armazenado. Além disso, ele permite atualizar silenciosamente o token de ID do Identity Platform de um usuário que já tenha sido autenticado anteriormente, sem exigir que esse usuário insira novamente as credenciais dele.
Se você estiver usando vários recursos de IAP com locatários diferentes, recomendamos usar uma instância de autenticação exclusiva para cada recurso. Desse modo, vários recursos com configurações diferentes poderão usar a mesma página de autenticação. Essa abordagem também permite que vários usuários façam login ao mesmo tempo sem desconectar o usuário anterior.
Veja a seguir um exemplo de como implementar o 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;
}
Como fazer login dos usuários
Para processar o login, implemente startSignIn(), mostre uma
UI para o usuário se autenticar e retorne um
UserCredential
para o usuário conectado ao concluir.
Em um ambiente multitenant, é possível determinar os métodos de autenticação disponíveis
em SelectedTenantInfo, se ele tiver sido fornecido. Essa variável contém
as mesmas informações retornadas por selectTenant().
O exemplo a seguir mostra uma implementação de startSignIn() para
um usuário atual com e-mail e senha:
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.
});
});
});
}
Também é possível fazer o login dos usuários com um provedor federado, como SAML ou OIDC, por meio de um pop-up ou redirecionamento:
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);
});
}
Alguns provedores de OAuth aceitam a transmissão de uma dica de login para conectar o usuário:
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.
});
});
}
Para mais informações, consulte Como autenticar com multitenancy.
Tratamento de erros
Para mostrar mensagens de erro aos usuários ou tentar se recuperar de erros como tempo limite de rede,
implemente handleError().
O exemplo a seguir 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;
});
}
A tabela abaixo lista os códigos de erro específicos da IAP que podem
ser retornados. O Identity Platform também pode retornar erros. Consulte a documentação de
firebase.auth.Auth.
| Código do erro | Descrição |
|---|---|
invalid-argument |
O cliente especificou um argumento inválido. |
failed-precondition |
A solicitação não pode ser executada no estado atual do sistema. |
out-of-range |
O cliente especificou um intervalo inválido. |
unauthenticated |
Solicitação não autenticada devido a um token de OAuth ausente, inválido ou expirado. |
permission-denied |
O cliente não tem permissão suficiente ou a IU está hospedada em um domínio não autorizado. |
not-found |
O recurso especificado não foi encontrado. |
aborted |
Conflito de simultaneidade, como leitura-modificação-gravação. |
already-exists |
O recurso que um cliente tentou criar já existe. |
resource-exhausted |
Excedeu a cota de recursos ou está perto de atingir a limitação de taxa. |
cancelled |
Solicitação cancelada pelo cliente. |
data-loss |
Perda ou corrupção de dados irrecuperável. |
unknown |
Erro desconhecido de servidor. |
internal |
Erro interno do servidor. |
not-implemented |
Método da API não implementado pelo servidor. |
unavailable |
Serviço indisponível. |
restart-process |
Acesse novamente o URL que redirecionou você para esta página para reiniciar o processo de autenticação. |
deadline-exceeded |
O prazo de solicitação foi excedido. |
authentication-uri-fail |
Falha ao gerar o URI de autenticação. |
gcip-token-invalid |
Token de ID do GCIP fornecido inválido. |
gcip-redirect-invalid |
URL de redirecionamento inválido. |
get-project-mapping-fail |
Falha ao receber o ID do projeto. |
gcip-id-token-encryption-error |
Erro de criptografia do token de ID do GCIP. |
gcip-id-token-decryption-error |
Erro de descriptografia do token de ID do GCIP. |
gcip-id-token-unescape-error |
Falha na função unescape de base64 segura para a Web. |
resource-missing-gcip-sign-in-url |
O URL de autenticação do GCIP está ausente para o recurso especificado do IAP. |
Personalizar a UI
É possível personalizar a página de autenticação com recursos opcionais, como barras de progresso e páginas de encerramento da sessão.
Como exibir uma IU de progresso
Para mostrar uma UI de progresso personalizada ao usuário sempre que o módulo gcip-iap executar tarefas de rede de longa duração,
implemente showProgressBar() e hideProgressBar().
Como desconectar os usuários
Em alguns casos, convém permitir que os usuários saiam de todas as sessões atuais que compartilham o mesmo URL de autenticação.
Depois que um usuário faz logout, talvez não haja um URL para redirecioná-lo de volta.
Isso geralmente acontece quando um usuário faz logout de todos os tenants associados a uma
página de login. Nesse caso, implemente completeSignOut() para
mostrar uma mensagem indicando que o usuário saiu da conta. Se você não implementar esse método, uma página em branco vai aparecer quando um usuário sair da conta.
Processando usuários
Para modificar um usuário conectado antes de redirecionar para o recurso IAP,
implemente processUser().
Use esse método para:
- vincular a outros provedores;
- atualizar o perfil do usuário;
- pedir mais dados do usuário após o registro;
- processar tokens de acesso de OAuth retornados por
getRedirectResult()depois de chamarsignInWithRedirect().
Veja abaixo um exemplo de como implementar o 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;
});
}
Se você quiser que as mudanças feitas em um usuário sejam refletidas nas declarações do token de ID propagadas pela IAP para seu app, force a atualização do 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;
});
}
Como testar a UI
Depois de criar uma classe que implementa AuthenticationHandler, você pode
usá-la para criar uma nova instância Authentication e iniciá-la:
// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();
Implante seu aplicativo e navegue até a página de autenticação. Você verá sua IU de login personalizada.
A seguir
- Saiba como acessar recursos que não são do Google de maneira programática.
- Saiba como gerenciar sessões.
- Saiba como as identidades externas funcionam com o IAP.