Neste artigo, você vai aprender a criar sua própria página de autenticação usando identidades externas e o IAP. Criar esta página por conta própria oferece controle total sobre o fluxo de autenticação e a experiência do usuário.
Se você não precisa personalizar totalmente sua UI, permita que o IAP hospede 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 Vou fornecer minha própria UI IU 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 outros recursos, como barras de progresso, páginas de desativação e processamento do usuário.
- Teste a 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 subjacentes entre a
UI e o 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 para v1.1.0
A partir da versão v1.0.0, o gcip-iap
requer a dependência de peer firebase
v9 ou mais recente.
Se você estiver migrando para a gcip-iap
v1.0.0 ou mais recente, conclua as seguintes
ações:
- Atualize a versão do
firebase
no arquivopackage.json
para v9.6.0 ou mais recente. - Atualize as instruções de importação
firebase
da 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 personalizado usando o formato do 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
firebase
no arquivopackage.json
para v9.8.3 ou mais recente. - Atualize as instruções de importação
firebase
da 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';
Como 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
.
Como selecionar locatários
Para selecionar um locatário, implemente selectTenant()
. É possível
implementar esse método para escolher um locatário de forma programática ou
exibir uma UI para que o usuário possa fazer a seleção.
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 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 multilocação.
Como selecionar locatários de maneira programática
Para selecionar um locatário de maneira programática, aproveite 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 que ele escolha um ou peça que ele insira o endereço de e-mail e, em seguida, encontre um correspondente 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 de Auth,
correspondente à chave de API e ao ID de locatário fornecidos. Se nenhum ID de locatário
for fornecido, use provedores de identidade no nível do projeto.
O getAuth()
rastreia o local em que 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 do 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 que o usuário faça a autenticação e retorne um
UserCredential
ao concluir o login.
Em um ambiente com vários locatários, é possível determinar os métodos de autenticação disponíveis
com SelectedTenantInfo
, caso tenha 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 um e-mail e uma 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.
});
});
}
Consulte Como autenticar com multilocatários para mais informações.
Como processar os erros
Para mostrar mensagens de erro aos usuários ou tentar recuperar erros, como o 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 do 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 desativaçã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 sai, talvez não haja um URL para redirecioná-lo de volta.
Isso geralmente ocorre quando um usuário sai de todos os locatários associados a uma
página de login. Nesse caso, implemente completeSignOut()
para
mostrar uma mensagem indicando que o usuário saiu com êxito. Se você não implementar esse método,
uma página em branco vai aparecer quando um usuário sair.
Processamento de usuários
Para modificar um usuário conectado antes de redirecionar para o recurso do IAP,
implemente processUser()
.
Use esse método para fazer o seguinte:
- 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 em um usuário sejam refletidas nas declarações de token de ID propagadas pelo IAP para seu app, será necessário forçar 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
, será possível
usá-la para criar uma nova instância de 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 identidades externas funcionam com o IAP.