Este artigo mostra como criar a sua própria página de autenticação com identidades externas e IAP. A criação desta página por si confere-lhe controlo total sobre o fluxo de autenticação e a experiência do utilizador.
Se não precisar de personalizar totalmente a IU, pode permitir que a IAP aloje uma página de início de sessão por si ou usar a FirebaseUI para uma experiência mais simplificada.
Vista geral
Para criar a sua própria página de autenticação, siga estes passos:
- Ative as identidades externas. Selecione Vou fornecer a minha própria opção de IU durante a configuração.
- Instale a biblioteca
gcip-iap
. - Configure a IU implementando a interface
AuthenticationHandler
. A sua página de autenticação tem de processar os seguintes cenários:- Seleção de inquilinos
- Autorização do utilizador
- Início de sessão do utilizador
- Processamento de erros
- Opcional: personalize a sua página de autenticação com funcionalidades adicionais, como barras de progresso, páginas de saída e processamento de utilizadores.
- Teste a IU.
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
NPM abstrai as comunicações entre a sua aplicação, a IAP e a Identity Platform. Isto permite-lhe personalizar todo o fluxo de autenticação sem ter de gerir as trocas subjacentes entre a IU e a IAP.
Use as importações corretas para a versão do seu 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 pares firebase
v9 ou superior.
Se estiver a migrar para a versão gcip-iap
1.0.0 ou superior, conclua as seguintes ações:
- Atualize a versão do
firebase
no ficheiropackage.json
para v9.6.0 ou superior. - Atualize as
firebase
declarações de importação da seguinte forma:
// 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 são necessárias alterações adicionais ao código.
gcip-iap v2.0.0
A partir da versão v2.0.0, o gcip-iap
requer a reescrita da sua aplicação de IU personalizada através do formato de SDK modular. Se estiver a migrar para a versão gcip-iap
2.0.0 ou superior, conclua as seguintes ações:
- Atualize a versão do
firebase
no ficheiropackage.json
para v9.8.3 ou superior. - Atualize as
firebase
declarações de importação da seguinte forma:
// 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 IU
Para configurar a IU, 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 inquilinos
Para selecionar um inquilino, implemente selectTenant()
. Pode implementar este método para escolher um inquilino programaticamente ou apresentar uma IU para que o utilizador possa selecionar um.
Em qualquer dos casos, a biblioteca usa o objeto SelectedTenantInfo
devolvido para concluir o fluxo de autenticação. Contém o ID do inquilino selecionado, os IDs de fornecedor e o email introduzido pelo utilizador.
Se tiver vários inquilinos no seu projeto, tem de selecionar um antes de poder autenticar um utilizador. Se tiver apenas um inquilino ou estiver a usar a autenticação ao nível do projeto, não precisa de implementar selectTenant()
.
O IAP suporta os mesmos fornecedores que a Identity Platform, como:
- Email e palavra-passe
- 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, personalizados e anónimos não são suportados para a multilocação.
Selecionar inquilinos programaticamente
Para selecionar um inquilino de forma programática, tire partido do contexto atual. A classe Authentication
contém getOriginalURL()
que devolve o URL ao qual o utilizador estava a aceder antes da autenticação.
Use esta opção para localizar uma correspondência numa lista de inquilinos 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 utilizadores selecionem inquilinos
Para permitir que o utilizador selecione um inquilino, apresente uma lista de inquilinos e peça ao utilizador para escolher um ou peça-lhe para introduzir o respetivo endereço de email e, em seguida, localize 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,
});
});
});
}
Autenticação de utilizadores
Depois de ter um fornecedor, implemente getAuth()
para devolver uma instância Auth, correspondente à chave da API e ao ID do inquilino fornecidos. Se não for fornecido nenhum ID do inquilino, use fornecedores de identidade ao nível do projeto.
getAuth()
acompanha a localização onde o utilizador correspondente à configuração fornecida está armazenado. Também permite atualizar silenciosamente o token de ID da Identity Platform de um utilizador autenticado anteriormente sem exigir que o utilizador introduza novamente as respetivas credenciais.
Se estiver a usar vários recursos de IAP com inquilinos diferentes, recomendamos que use uma instância de autenticação exclusiva para cada recurso. Isto permite que vários recursos com configurações diferentes usem a mesma página de autenticação. Também permite que vários utilizadores iniciem sessão em simultâneo sem terminar a sessão do utilizador anterior.
Segue-se um exemplo de como 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;
}
Iniciar sessão dos utilizadores
Para processar o início de sessão, implemente startSignIn()
, apresente uma IU para o utilizador se autenticar e, em seguida, devolva um UserCredential
para o utilizador com sessão iniciada na conclusão.
Num ambiente multiinquilino, pode determinar os métodos de autenticação disponíveis
a partir de SelectedTenantInfo
, se tiver sido fornecido. Esta variável contém as mesmas informações devolvidas por selectTenant()
.
O exemplo seguinte mostra uma implementação de startSignIn()
para um utilizador existente com um email e uma palavra-passe:
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 pode iniciar sessão nos utilizadores com um fornecedor federado, como SAML ou OIDC, através de um pop-up ou um 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 fornecedores de OAuth suportam a transmissão de uma sugestão de início de sessão:
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 o artigo Autenticação com multi-tenancy para mais informações.
Processamento de erros
Para apresentar mensagens de erro aos utilizadores ou tentar a recuperação de erros, como limites de tempo de rede,
implemente handleError()
.
O exemplo seguinte 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 apresenta códigos de erro específicos da IAP que podem ser devolvidos. O Identity Platform também pode devolver erros. Consulte a documentação para firebase.auth.Auth
.
Código de erro | Descrição |
---|---|
invalid-argument |
O cliente especificou um argumento inválido. |
failed-precondition |
Não é possível executar o pedido no estado atual do sistema. |
out-of-range |
O cliente especificou uma alteração inválida. |
unauthenticated |
Pedido não autenticado devido a um token OAuth em falta, inválido ou expirado. |
permission-denied |
O cliente não tem autorização suficiente ou a IU está alojada num domínio não autorizado. |
not-found . |
Não foi possível encontrar o recurso especificado. |
aborted |
Conflito de simultaneidade, tal como conflito de ler-modificar-gravar. |
already-exists |
O recurso que um cliente tentou criar já existe. |
resource-exhausted |
Sem quota de recursos ou a atingir a limitação de velocidade. |
cancelled |
Pedido cancelado pelo cliente. |
data-loss |
Perda de dados irrecuperável ou corrupção de dados. |
unknown |
Erro de servidor desconhecido. |
internal |
Erro interno do servidor. |
not-implemented |
Método de API não implementado pelo servidor. |
unavailable |
Serviço indisponível. |
restart-process |
Aceda novamente ao URL que o redirecionou para esta página, para reiniciar o processo de autenticação. |
deadline-exceeded |
Prazo do pedido excedido. |
authentication-uri-fail |
Falha ao gerar o URI de autenticação. |
gcip-token-invalid |
Foi fornecido um token de ID do GCIP inválido. |
gcip-redirect-invalid |
URL de redirecionamento inválido. |
get-project-mapping-fail |
Não foi possível obter o ID do projeto. |
gcip-id-token-encryption-error |
Erro de encriptação do token de ID da GCIP. |
gcip-id-token-decryption-error |
Erro de desencriptação do token de ID da GCIP. |
gcip-id-token-unescape-error |
Falha ao anular a escape base64 segura para a Web. |
resource-missing-gcip-sign-in-url |
URL de autenticação do GCIP em falta para o recurso do IAP especificado. |
Personalizar a IU
Pode personalizar a página de autenticação com funcionalidades opcionais, como barras de progresso e páginas de encerramento de sessão.
Apresentar uma IU de progresso
Para apresentar uma IU de progresso personalizada ao utilizador sempre que o módulo gcip-iap
executar tarefas de rede de longa duração, implemente showProgressBar()
e hideProgressBar()
.
Terminar sessão dos utilizadores
Em alguns casos, pode querer permitir que os utilizadores terminem sessão em todas as sessões atuais que partilham o mesmo URL de autenticação.
Depois de um utilizador terminar sessão, pode não existir um URL para o redirecionar de volta.
Isto ocorre normalmente quando um utilizador termina sessão em todos os inquilinos associados a uma página de início de sessão. Neste caso, implemente completeSignOut()
para
apresentar uma mensagem a indicar que o utilizador terminou sessão com êxito. Se não implementar este método,
é apresentada uma página em branco quando um utilizador termina sessão.
Processamento de utilizadores
Para modificar um utilizador com sessão iniciada antes de redirecionar para o recurso de IAP,
implemente processUser()
.
Pode usar este método para fazer o seguinte:
- Link para fornecedores adicionais.
- Atualize o perfil do utilizador.
- Pedir dados adicionais ao utilizador após o registo.
- Processe as chaves de acesso OAuth devolvidas por
getRedirectResult()
depois de chamarsignInWithRedirect()
.
Segue-se um exemplo de implementação 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;
});
}
Se quiser que as alterações a um utilizador se reflitam nas reivindicações do token de ID propagadas pela IAP para a sua app, tem de 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;
});
}
Testar a IU
Depois de criar uma classe que implemente AuthenticationHandler
, 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();
Implemente a sua aplicação e navegue para a página de autenticação. Deve ver a IU de início de sessão personalizada.
O que se segue?
- Saiba como aceder a recursos não pertencentes à Google através de programação.
- Saiba como gerir sessões.
- Saiba como as identidades externas funcionam com a IAP.