Adicionar autenticação ao seu aplicativo Auth.js (Next Auth)
Este guia mostrará como integrar o Logto em seu aplicativo Next.js com Auth.js, anteriormente conhecido como Next Auth.
- Neste guia, assumimos que você configurou o Next Auth em seu projeto Next.js. Se você ainda não fez isso, confira a documentação do Next Auth para começar.
Pré-requisitos
- Uma conta Logto Cloud ou um Logto auto-hospedado.
- Um aplicativo tradicional Logto criado.
- Um projeto Next.js com Auth.js, confira a documentação do Auth.js.
Instalação
Instale Auth.js através do seu gerenciador de pacotes favorito:
- npm
- pnpm
- yarn
npm i next-auth@beta
pnpm add next-auth@beta
yarn add next-auth@beta
Veja a documentação do Auth.js para mais detalhes.
Integração
Configurar o provedor Auth.js
Você pode encontrar e copiar o "App Secret" na página de detalhes do aplicativo no Admin Console:
Modifique sua configuração de rota de API do Auth.js, adicione Logto como um provedor OIDC:
- Auth.js v5
- Next Auth v4
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oidc',
// Você pode obter o valor do emissor na página de Detalhes do Aplicativo Logto,
// no campo "Endpoint do emissor"
issuer: 'https://xxxx.logto.app/oidc',
clientId: '<logto-app-id>',
clientSecret: '<logto-app-secret>',
authorization: {
params: { scope: 'openid offline_access profile email' },
},
profile(profile) {
// Você pode personalizar o mapeamento do perfil do usuário aqui
return {
id: profile.sub,
name: profile.name ?? profile.username,
email: profile.email,
image: profile.picture,
};
},
},
],
});
- Substitua a URL do
issuer
pelo "Endpoint do emissor" do seu aplicativo Logto. - Substitua o
clientId
e oclientSecret
pelo ID e segredo do seu aplicativo Logto. - Personalize a função
profile
para mapear o perfil do usuário para o objeto de usuário do Next Auth, o mapeamento padrão é mostrado no exemplo.
Então, você também pode adicionar um Middleware opcional para manter a sessão ativa:
export { auth as middleware } from '@/auth';
import NextAuth from 'next-auth';
const handler = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oauth',
// Você pode obter a URL bem conhecida na página de Detalhes do Aplicativo Logto,
// no campo "Endpoint de configuração do provedor OpenID"
wellKnown: 'https://xxxx.logto.app/oidc/.well-known/openid-configuration',
authorization: { params: { scope: 'openid offline_access profile email' } },
clientId: '<logto-app-id>',
clientSecret: '<logto-app-secret>',
client: {
id_token_signed_response_alg: 'ES384',
},
profile(profile) {
// Você pode personalizar o mapeamento do perfil do usuário aqui
return {
id: profile.sub,
name: profile.name ?? profile.username,
email: profile.email,
image: profile.picture,
};
},
},
],
});
export { handler as GET, handler as POST };
- Substitua a URL do
wellKnown
pelo "Endpoint de configuração do provedor OpenID" do seu aplicativo Logto. - Substitua o
clientId
e oclientSecret
pelo ID e segredo do seu aplicativo Logto. - Personalize a função
profile
para mapear o perfil do usuário para o objeto de usuário do Next Auth, o mapeamento padrão é mostrado no exemplo. - Lembre-se de definir o
id_token_signed_response_alg
comoES384
.
Você pode encontrar mais detalhes na documentação do Auth.js.
Configurar URI de redirecionamento de login
Antes de mergulharmos nos detalhes, aqui está uma visão geral rápida da experiência do usuário final. O processo de login pode ser simplificado da seguinte forma:
- Seu aplicativo invoca o método de login.
- O usuário é redirecionado para a página de login do Logto. Para aplicativos nativos, o navegador do sistema é aberto.
- O usuário faz login e é redirecionado de volta para o seu aplicativo (configurado como o URI de redirecionamento).
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.
Nos trechos de código a seguir, assumimos que seu aplicativo está sendo executado em http://localhost:3000/
.
Vamos mudar para a página de detalhes do Aplicativo no Logto Console. Adicione um URI de redirecionamento http://localhost:3000/api/auth/callback/logto
e clique em "Salvar alterações".
Implementar login e logout
Implementar botão de login e logout
import { signIn } from '@/auth';
export default function SignIn() {
return (
<form
action={async () => {
'use server';
await signIn('logto');
}}
>
<button type="submit">Sign In</button>
</form>
);
}
import { signOut } from '@/auth';
export function SignOut() {
return (
<form
action={async () => {
'use server';
await signOut();
}}
>
<button type="submit">Sign Out</button>
</form>
);
}
Mostrar botão de login e logout na página
import SignIn from './components/sign-in';
import SignOut from './components/sign-out';
import { auth } from '@/auth';
export default function Home() {
const session = await auth();
return <div>{session?.user ? <SignOut /> : <SignIn />}</div>;
}
Acima está um exemplo simples, você pode verificar a documentação do Auth.js para mais detalhes.
Ponto de verificação
Agora, você pode testar seu aplicativo para ver se a autenticação funciona como esperado.
Buscar informações do usuário
Exibir informações do usuário
Quando o usuário está autenticado, o valor de retorno de auth()
será um objeto contendo as informações do usuário. Você pode exibir essas informações em seu aplicativo:
import { auth } from '@/auth';
export default async function Home() {
const session = await auth();
return (
<main>
{session?.user && (
<div>
<h2>Reivindicações (Claims):</h2>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
</tr>
</thead>
<tbody>
{Object.entries(session.user).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{String(value)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</main>
);
}
Solicitar reivindicações adicionais
Você pode perceber que algumas informações do usuário estão faltando no objeto retornado de auth()
. 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 os parâmetros do provedor Logto:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
},
},
// ...
},
],
});
Reivindicações que precisam de solicitações de rede
Para evitar sobrecarregar o Token de ID, 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ê precisa fazer uma solicitação de rede para buscar as informações do usuário.
Obter token de acesso
Atualize a configuração do NextAuth
para que possamos obter o token de acesso:
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
// Injeta o token de acesso no objeto da sessão
session.accessToken = token.accessToken;
return session;
},
},
});
Buscar informações do usuário
Agora acesse o endpoint de informações do usuário OIDC com o token de acesso:
// ...
export default async function Home() {
const session = await auth();
// Substitua a URL pelo seu endpoint Logto, deve terminar com `/oidc/me`
const response = await fetch('https://xxx.logto.app/oidc/me', {
headers: {
Authorization: `Bearer ${session?.accessToken}`,
},
});
const user = await response.json();
console.log(user);
// ...
}
Acima está um exemplo simples. Lembre-se de lidar com os casos de erro.
Atualização do token de acesso
Um token de acesso é válido por um curto período de tempo. Por padrão, o Next.js buscará apenas um quando a sessão for criada. Para implementar a atualização automática do token de acesso, veja Rotação de token de atualização.
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 suportados e as reivindicações 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 referir-se especificamente a 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 |
roles
Nome da reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
roles | string[] | Os papéis do usuário | Não |
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 das organizações às 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 provedor Logto
Depois de configurar os recursos de API, você pode adicioná-los ao configurar o Logto em seu aplicativo:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
resource: 'https://shopping.your-app.com/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 NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email shopping:read shopping:write',
resource: 'https://shopping.your-app.com/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 NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile read write',
resource: 'https://shopping.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
Auth.js buscará o token de acesso apenas uma vez sem o parâmetro de recurso. Precisamos implementar a busca do token de acesso por conta própria.
Obter token de atualização
Atualize a configuração do provedor Logto, adicione o parâmetro "prompt" e defina-o como consent
, e certifique-se de que o escopo offline_access
esteja incluído:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
authorization: {
params: {
prompt: 'consent',
scope: 'openid offline_access shopping:read shopping:write',
resource: 'https://shopping.your-app.com/api',
// ...
},
},
// ...
});
Em seguida, adicione um callback para salvar o refresh_token
na sessão:
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
callbacks: {
async jwt({ token, account }) {
if (account) {
// ...
token.refreshToken = account.refresh_token;
}
return token;
},
async session({ session, token }) {
// ...
session.refreshToken = token.refreshToken;
return session;
},
},
});
Buscar token de acesso
Com o refresh_token
, podemos buscar o token de acesso do endpoint de token OIDC do Logto.
// ...
export default async function Home() {
const session = await auth();
if (session?.refreshToken) {
// Substitua o ID e o segredo do aplicativo pelo seu próprio, você pode verificar a seção "Integration".
const basicAuth = Buffer.from('<logto-app-id>:<logto-app-secret>').toString('base64');
// Substitua a URL pelo seu endpoint Logto, deve terminar com `/oidc/token`
const response = await fetch('https://xxx.logto.app/oidc/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: session.refreshToken,
resource: 'https://shopping.your-app.com/api',
}).toString(),
});
const data = await response.json();
console.log(data.access_token);
}
// ...
}
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 urn:logto:scope:organizations
ao configurar o cliente Logto:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
// ...
authorization: {
params: {
scope: 'openid offline_access urn:logto:scope:organizations',
},
},
// ...
},
],
});
Uma vez que o usuário esteja autenticado, você pode buscar o token de organização para o usuário:
Similar ao token de acesso para recursos de API, podemos usar o token de atualização para buscar o token de acesso da organização.
// ...
export default async function Home() {
const session = await auth();
if (session?.refreshToken) {
// Substitua o ID e o segredo do aplicativo pelos seus próprios, você pode verificar a seção "Integration".
const basicAuth = Buffer.from('<logto-app-id>:<logto-app-secret>').toString('base64');
// Substitua a URL pelo seu endpoint Logto, deve terminar com `/oidc/token`
const response = await fetch('https://xxx.logto.app/oidc/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: session.refreshToken,
resource: 'urn:logto:scope:organizations',
organization_id: 'organization-id',
}).toString(),
});
const data = await response.json();
console.log(data.access_token);
}
// ...
}
Leituras adicionais
Fluxos do usuário final: fluxos de autenticação, fluxos de conta e fluxos de organização Configurar conectores Proteger sua APIMigrando a integração do Logto do NextAuth.js v4 para v5