Pular para o conteúdo principal

Adicionar autenticação ao seu aplicativo de extensão Chrome

Este guia mostrará como integrar o Logto na sua extensão Chrome.

dica
  • 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

Instalação

npm i @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
  1. 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.
  2. 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:

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.
nota

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:

service-worker.js
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.

info

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:

service-worker.js
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.

  1. Vá para a página do aplicativo no Logto Console.
  2. Na seção "Redirect URIs", adicione o URI: https://<extension-id>.chromiumapp.org/callback.
  3. Na seção "Post sign-out redirect URIs", adicione o URI: https://<extension-id>.chromiumapp.org/.
  4. 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.
  5. 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:

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):

popup.js
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:

  1. Abra o popup da extensão.
  2. Clique no botão "Entrar".
  3. Você será redirecionado para a página de login do Logto.
  4. Faça login com sua conta Logto.
  5. 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:

popup.js
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:

popup.js
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:

popup.js
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:

Página de popup

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:

Home.js
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.

info

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:

dica

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:

index.js
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`
Este método buscará as informações do usuário solicitando ao endpoint userinfo. Para saber mais sobre os escopos e reivindicações disponíveis, veja a seção Escopos e reivindicações.

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çãoTipoDescriçãoPrecisa de userinfo?
substringO identificador único do usuárioNão

profile

Nome da ReivindicaçãoTipoDescriçãoPrecisa de userinfo?
namestringO nome completo do usuárioNão
usernamestringO nome de usuário do usuárioNão
picturestringURL 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_atnumberHora 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_atnumberHora 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.

nota

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çãoTipoDescriçãoPrecisa de userinfo?
emailstringO endereço de email do usuárioNão
email_verifiedbooleanSe o endereço de email foi verificadoNão

phone

Nome da ReivindicaçãoTipoDescriçãoPrecisa de userinfo?
phone_numberstringO número de telefone do usuárioNão
phone_number_verifiedbooleanSe o número de telefone foi verificadoNã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çãoTipoDescriçãoPrecisa de userinfo?
custom_dataobjectOs dados personalizados do usuárioSim

identities

Nome da ReivindicaçãoTipoDescriçãoPrecisa de userinfo?
identitiesobjectAs identidades vinculadas do usuárioSim
sso_identitiesarrayAs identidades SSO vinculadas do usuárioSim

urn:logto:scope:organizations

Nome da ReivindicaçãoTipoDescriçãoPrecisa de userinfo?
organizationsstring[]Os IDs das organizações às quais o usuário pertenceNão
organization_dataobject[]Os dados das organizações às quais o usuário pertenceSim

urn:logto:scope:organization_roles

Nome da ReivindicaçãoTipoDescriçãoPrecisa de userinfo?
organization_rolesstring[]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:

index.js
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:

index.js
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:

index.js
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.

nota

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:

index.js
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:

index.js
// 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.

nota

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.

Leituras adicionais

Fluxos do usuário final: fluxos de autenticação, fluxos de conta e fluxos de organização Configurar conectores Proteger sua API