Adicionar autenticação ao seu aplicativo de extensão Chrome
Este guia mostrará como integrar o Logto na sua extensão Chrome.
- A demonstração a seguir foi testada no Chrome v123.0.6312.87 (arm64). Outras versões também devem funcionar, desde que suportem as APIs
chrome
usadas no SDK. - O projeto de exemplo está disponível em nosso repositório GitHub.
Pré-requisitos
- Uma conta Logto Cloud ou um Logto auto-hospedado.
- Um aplicativo de página única (SPA) criado no Logto Console.
- Um projeto de extensão Chrome.
Instalação
- npm
- Yarn
- pnpm
npm i @logto/chrome-extension
yarn add @logto/chrome-extension
pnpm add @logto/chrome-extension
Integração
O fluxo de autenticação
Supondo que você coloque um botão "Entrar" no popup da sua extensão do Chrome, o fluxo de autenticação será assim:
Para outras páginas interativas na sua extensão, você só precisa substituir o participante Popup da extensão
pelo nome da página. Neste tutorial, vamos focar na página de popup.
Sobre o login baseado em redirecionamento
- Este processo de autenticação segue o protocolo OpenID Connect (OIDC), e o Logto aplica medidas de segurança rigorosas para proteger o login do usuário.
- Se você tiver vários aplicativos, pode usar o mesmo provedor de identidade (Logto). Uma vez que o usuário faz login em um aplicativo, o Logto completará automaticamente o processo de login quando o usuário acessar outro aplicativo.
Para saber mais sobre a lógica e os benefícios do login baseado em redirecionamento, veja Experiência de login do Logto explicada.
Atualizar o manifest.json
O Logto SDK requer as seguintes permissões no manifest.json
:
{
"permissions": ["identity", "storage"],
"host_permissions": ["https://*.logto.app/*"]
}
permissions.identity
: Necessário para a Chrome Identity API, que é usada para login e logout.permissions.storage
: Necessário para armazenar a sessão do usuário.host_permissions
: Necessário para o Logto SDK se comunicar com as APIs do Logto.
Se você estiver usando um domínio personalizado no Logto Cloud, precisará atualizar o host_permissions
para corresponder ao seu domínio.
Configurar um script de fundo (service worker)
No script de fundo da sua extensão do Chrome, inicialize o Logto SDK:
import LogtoClient from '@logto/chrome-extension';
export const logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>'
appId: '<your-logto-app-id>',
});
Substitua <your-logto-endpoint>
e <your-logto-app-id>
pelos valores reais. Você pode encontrar esses valores na página do aplicativo que acabou de criar no Logto Console.
Se você não tiver um script de fundo, pode seguir o guia oficial para criar um.
Por que precisamos de um script de fundo?
Páginas normais de extensão, como o popup ou a página de opções, não podem ser executadas em segundo plano e podem ser fechadas durante o processo de autenticação. Um script de fundo garante que o processo de autenticação possa ser tratado adequadamente.
Em seguida, precisamos ouvir a mensagem de outras páginas da extensão e lidar com o processo de autenticação:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// No código abaixo, como retornamos `true` para cada ação, precisamos chamar `sendResponse`
// para notificar o remetente. Você também pode lidar com erros aqui ou usar outras maneiras de notificar o remetente.
if (message.action === 'signIn') {
const redirectUri = chrome.identity.getRedirectURL('/callback');
logtoClient.signIn(redirectUri).finally(sendResponse);
return true;
}
if (message.action === 'signOut') {
const redirectUri = chrome.identity.getRedirectURL();
logtoClient.signOut(redirectUri).finally(sendResponse);
return true;
}
return false;
});
Você pode notar que há dois URIs de redirecionamento usados no código acima. Ambos são criados por chrome.identity.getRedirectURL
, que é uma API integrada do Chrome para gerar um URL de redirecionamento para fluxos de autenticação. Os dois URIs serão:
https://<extension-id>.chromiumapp.org/callback
para login.https://<extension-id>.chromiumapp.org/
para logout.
Observe que esses URIs não são acessíveis e são usados apenas para o Chrome acionar ações específicas para o processo de autenticação.
Atualizar as configurações do aplicativo Logto
Agora precisamos atualizar as configurações do aplicativo Logto para permitir os URIs de redirecionamento que acabamos de criar.
- Vá para a página do aplicativo no Logto Console.
- Na seção "Redirect URIs", adicione o URI:
https://<extension-id>.chromiumapp.org/callback
. - Na seção "Post sign-out redirect URIs", adicione o URI:
https://<extension-id>.chromiumapp.org/
. - Na seção "CORS allowed origins", adicione o URI:
chrome-extension://<extension-id>
. O SDK na extensão do Chrome usará essa origem para se comunicar com as APIs do Logto. - Clique em Salvar alterações.
Lembre-se de substituir <extension-id>
pelo seu ID de extensão real. Você pode encontrar o ID da extensão na página chrome://extensions
.
Adicionar botões de login e logout ao popup
Estamos quase lá! Vamos adicionar os botões de login e logout e outras lógicas necessárias à página de popup.
No arquivo popup.html
:
<button id="sign-in">Entrar</button> <button id="sign-out">Sair</button>
No arquivo popup.js
(supondo que popup.js
esteja incluído no popup.html
):
document.getElementById('sign-in').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signIn' });
// Login concluído (ou falhou), você pode atualizar a UI aqui.
});
document.getElementById('sign-out').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signOut' });
// Logout concluído (ou falhou), você pode atualizar a UI aqui.
});
Ponto de verificação: Testar o fluxo de autenticação
Agora você pode testar o fluxo de autenticação na sua extensão do Chrome:
- Abra o popup da extensão.
- Clique no botão "Entrar".
- Você será redirecionado para a página de login do Logto.
- Faça login com sua conta Logto.
- Você será redirecionado de volta para o Chrome.
Verificar estado de autenticação
Como o Chrome fornece APIs de armazenamento unificadas, além do fluxo de login e logout, todos os outros métodos do Logto SDK podem ser usados diretamente na página de popup.
No seu popup.js
, você pode reutilizar a instância LogtoClient
criada no script de fundo ou criar uma nova com a mesma configuração:
import LogtoClient from '@logto/chrome-extension';
const logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>'
appId: '<your-logto-app-id>',
});
// Ou reutilize a instância logtoClient criada no script de fundo
import { logtoClient } from './service-worker.js';
Em seguida, você pode criar uma função para carregar o estado de autenticação e o perfil do usuário:
const loadAuthenticationState = async () => {
const isAuthenticated = await logtoClient.isAuthenticated();
// Atualize a UI com base no estado de autenticação
if (isAuthenticated) {
const user = await logtoClient.getIdTokenClaims(); // { sub: '...', email: '...', ... }
// Atualize a UI com o perfil do usuário
}
};
Você também pode combinar a função loadAuthenticationState
com a lógica de login e logout:
document.getElementById('sign-in').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signIn' });
await loadAuthenticationState();
});
document.getElementById('sign-out').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signOut' });
await loadAuthenticationState();
});
Aqui está um exemplo da página de popup com o estado de autenticação:
Outras considerações
- Empacotamento do service worker: Se você usar um empacotador como Webpack ou Rollup, precisará definir explicitamente o alvo como
browser
ou similar para evitar o empacotamento desnecessário de módulos Node.js. - Resolução de módulos: O SDK de extensão do Chrome do Logto é um módulo apenas ESM.
Veja nosso projeto de exemplo para um exemplo completo com TypeScript, Rollup e outras configurações.
Obter informações do usuário
Exibir informações do usuário
Para exibir as informações do usuário, você pode usar o método logtoClient.getIdTokenClaims()
. Por exemplo, na sua página inicial:
const userInfo = await logtoClient.getIdTokenClaims();
// Gerar tabela de exibição para reivindicações do token de ID
const table = document.createElement('table');
const thead = document.createElement('thead');
const tr = document.createElement('tr');
const thName = document.createElement('th');
const thValue = document.createElement('th');
thName.innerHTML = 'Nome';
thValue.innerHTML = 'Valor';
tr.append(thName, thValue);
thead.append(tr);
table.append(thead);
const tbody = document.createElement('tbody');
for (const [key, value] of Object.entries(userInfo)) {
const tr = document.createElement('tr');
const tdName = document.createElement('td');
const tdValue = document.createElement('td');
tdName.innerHTML = key;
tdValue.innerHTML = typeof value === 'string' ? value : JSON.stringify(value);
tr.append(tdName, tdValue);
tbody.append(tr);
}
table.append(tbody);
Solicitar reivindicações adicionais
Você pode perceber que algumas informações do usuário estão faltando no objeto retornado de getIdTokenClaims()
. Isso ocorre porque OAuth 2.0 e OpenID Connect (OIDC) são projetados para seguir o princípio do menor privilégio (PoLP), e o Logto é construído com base nesses padrões.
Por padrão, reivindicações limitadas são retornadas. Se você precisar de mais informações, pode solicitar escopos adicionais para acessar mais reivindicações.
Uma "reivindicação (Claim)" é uma afirmação feita sobre um sujeito; um "escopo (Scope)" é um grupo de reivindicações. No caso atual, uma reivindicação é uma informação sobre o usuário.
Aqui está um exemplo não normativo da relação escopo - reivindicação:
A reivindicação "sub" significa "sujeito (Subject)", que é o identificador único do usuário (ou seja, ID do usuário).
O Logto SDK sempre solicitará três escopos: openid
, profile
e offline_access
.
Para solicitar escopos adicionais, você pode configurar as configurações do Logto:
import LogtoClient, { UserScope } from '@logto/browser';
const logtoClient = new LogtoClient({
appId: '<your-application-id>',
endpoint: '<your-logto-endpoint>',
scopes: [UserScope.Email, UserScope.Phone],
});
Então você pode acessar as reivindicações adicionais no valor de retorno de logtoClient.getIdTokenClaims()
:
const claims = await getIdTokenClaims();
// Agora você pode acessar as reivindicações adicionais `claims.email`, `claims.phone`, etc.
Reivindicações que precisam de solicitações de rede
Para evitar o inchaço do Token de ID (ID token), algumas reivindicações requerem solicitações de rede para serem buscadas. Por exemplo, a reivindicação custom_data
não está incluída no objeto do usuário, mesmo que seja solicitada nos escopos. Para acessar essas reivindicações, você pode usar o método logtoClient.fetchUserInfo()
:
const userInfo = await logtoClient.fetchUserInfo();
// Agora você pode acessar a reivindicação `userInfo.custom_data`
Escopos e reivindicações
Logto usa as convenções de escopos e reivindicações do OIDC para definir os escopos e reivindicações para recuperar informações do usuário do Token de ID e do endpoint userinfo do OIDC. Tanto "escopo" quanto "reivindicação" são termos das especificações OAuth 2.0 e OpenID Connect (OIDC).
Aqui está a lista de Escopos (Scopes) suportados e as Reivindicações (Claims) correspondentes:
openid
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
sub | string | O identificador único do usuário | Não |
profile
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
name | string | O nome completo do usuário | Não |
username | string | O nome de usuário do usuário | Não |
picture | string | URL da foto de perfil do Usuário Final. Este URL DEVE referir-se a um arquivo de imagem (por exemplo, um arquivo de imagem PNG, JPEG ou GIF), em vez de uma página da Web contendo uma imagem. Note que este URL DEVE referenciar especificamente uma foto de perfil do Usuário Final adequada para exibição ao descrever o Usuário Final, em vez de uma foto arbitrária tirada pelo Usuário Final. | Não |
created_at | number | Hora em que o Usuário Final foi criado. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z). | Não |
updated_at | number | Hora em que as informações do Usuário Final foram atualizadas pela última vez. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z). | Não |
Outras reivindicações padrão incluem family_name
, given_name
, middle_name
, nickname
, preferred_username
, profile
, website
, gender
, birthdate
, zoneinfo
e locale
também serão incluídas no escopo profile
sem a necessidade de solicitar o endpoint userinfo. Uma diferença em relação às reivindicações acima é que essas reivindicações só serão retornadas quando seus valores não estiverem vazios, enquanto as reivindicações acima retornarão null
se os valores estiverem vazios.
Ao contrário das reivindicações padrão, as reivindicações created_at
e updated_at
estão usando milissegundos em vez de segundos.
email
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
string | O endereço de email do usuário | Não | |
email_verified | boolean | Se o endereço de email foi verificado | Não |
phone
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
phone_number | string | O número de telefone do usuário | Não |
phone_number_verified | boolean | Se o número de telefone foi verificado | Não |
address
Por favor, consulte o OpenID Connect Core 1.0 para os detalhes da reivindicação de endereço.
custom_data
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
custom_data | object | Os dados personalizados do usuário | Sim |
identities
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
identities | object | As identidades vinculadas do usuário | Sim |
sso_identities | array | As identidades SSO vinculadas do usuário | Sim |
urn:logto:scope:organizations
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
organizations | string[] | Os IDs das organizações às quais o usuário pertence | Não |
organization_data | object[] | Os dados das organizações às quais o usuário pertence | Sim |
urn:logto:scope:organization_roles
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
organization_roles | string[] | Os papéis da organização aos quais o usuário pertence com o formato <organization_id>:<role_name> | Não |
Considerando o desempenho e o tamanho dos dados, se "Precisa de userinfo?" for "Sim", isso significa que a reivindicação não aparecerá no Token de ID, mas será retornada na resposta do endpoint userinfo.
Recursos de API
Recomendamos ler 🔐 Controle de Acesso Baseado em Papel (RBAC) primeiro para entender os conceitos básicos do RBAC do Logto e como configurar corretamente os recursos de API.
Configurar cliente Logto
Depois de configurar os recursos de API, você pode adicioná-los ao configurar o Logto em seu aplicativo:
import LogtoClient from '@logto/browser';
const logtoClient = new LogtoClient({
// ...other configs
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // Adicionar recursos de API
});
Cada recurso de API tem suas próprias permissões (escopos).
Por exemplo, o recurso https://shopping.your-app.com/api
tem as permissões shopping:read
e shopping:write
, e o recurso https://store.your-app.com/api
tem as permissões store:read
e store:write
.
Para solicitar essas permissões, você pode adicioná-las ao configurar o Logto em seu aplicativo:
import LogtoClient from '@logto/chrome-extension';
const logtoClient = new LogtoClient({
// ...other configs
scopes: ['shopping:read', 'shopping:write', 'store:read', 'store:write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // Adicionar recursos de API
});
Você pode notar que os escopos são definidos separadamente dos recursos de API. Isso ocorre porque Resource Indicators for OAuth 2.0 especifica que os escopos finais para a solicitação serão o produto cartesiano de todos os escopos em todos os serviços de destino.
Assim, no caso acima, os escopos podem ser simplificados a partir da definição no Logto, ambos os recursos de API podem ter escopos read
e write
sem o prefixo. Então, na configuração do Logto:
import LogtoClient, { UserScope } from '@logto/chrome-extension';
const logtoClient = new LogtoClient({
// ...other configs
scopes: ['read', 'write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
});
Para cada recurso de API, ele solicitará os escopos read
e write
.
Não há problema em solicitar escopos que não estão definidos nos recursos de API. Por exemplo, você pode solicitar o escopo email
mesmo que os recursos de API não tenham o escopo email
disponível. Escopos indisponíveis serão ignorados com segurança.
Após o login bem-sucedido, o Logto emitirá os escopos apropriados para os recursos de API de acordo com os papéis do usuário.
Buscar token de acesso para o recurso de API
Para buscar o token de acesso para um recurso de API específico, você pode usar o método getAccessToken
:
const accessToken = await logtoClient.getAccessToken('https://store.your-app.com/api');
console.log('Token de acesso', accessToken);
Este método retornará um token de acesso JWT que pode ser usado para acessar o recurso de API quando o usuário tiver as permissões relacionadas. Se o token de acesso em cache atual tiver expirado, este método tentará automaticamente usar um token de atualização para obter um novo token de acesso.
Buscar tokens de organização
Se organização é um conceito novo para você, por favor, leia 🏢 Organizações (Multi-tenancy) para começar.
Você precisa adicionar o escopo UserScope.Organizations
ao configurar o cliente Logto:
import LogtoClient, { UserScope } from '@logto/chrome-extension';
const logtoClient = new LogtoClient({
// ...outras configurações
scopes: [UserScope.Organizations],
});
Uma vez que o usuário esteja autenticado, você pode buscar o token de organização para o usuário:
// Obter organizationIds do userInfo
const claims = await logtoClient.getIdTokenClaims();
const organizationIds = claims.organizations;
/**
* Ou dos claims do token de ID
*
* const claims = await logtoClient.getIdTokenClaims();
* const organizationIds = claims.organizations;
*/
// Obter o token de acesso da organização
if (organizationIds.length > 0) {
const organizationId = organizationIds[0];
const organizationAccessToken = await logtoClient.getOrganizationToken(organizationId);
console.log('Token de acesso da organização', organizationAccessToken);
}
./code/_scopes-and-claims-code.mdx./code/_config-organization-code.mdx
Anexar token de acesso aos cabeçalhos da solicitação
Coloque o token no campo Authorization
dos cabeçalhos HTTP com o formato Bearer (Bearer YOUR_TOKEN
), e você está pronto para prosseguir.
O fluxo de integração do Bearer Token pode variar com base no framework ou solicitante que você está usando. Escolha sua própria maneira de aplicar o cabeçalho de solicitação Authorization
.