Pular para o conteúdo principal

Como validar tokens de acesso no seu serviço de API ou backend

Validar tokens de acesso é uma parte crítica para impor o controle de acesso baseado em papel (RBAC) no Logto. Este guia mostra como verificar JWTs emitidos pelo Logto no seu backend / API, checando assinatura, emissor, público, expiração, permissões (escopos) e contexto de organização.

Antes de começar

Passo 1: Inicialize constantes e utilitários

Defina as constantes e utilitários necessários em seu código para lidar com a extração e validação do token. Uma requisição válida deve incluir um cabeçalho Authorization no formato Bearer <access_token>.

auth-middleware.ts
import { IncomingHttpHeaders } from 'http';

const JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks';
const ISSUER = 'https://your-tenant.logto.app/oidc';

export class AuthInfo {
constructor(
public sub: string,
public clientId?: string,
public organizationId?: string,
public scopes: string[] = [],
public audience: string[] = []
) {}
}

export class AuthorizationError extends Error {
name = 'AuthorizationError';
constructor(
message: string,
public status = 403
) {
super(message);
}
}

export function extractBearerTokenFromHeaders({ authorization }: IncomingHttpHeaders): string {
const bearerPrefix = 'Bearer ';

if (!authorization) {
throw new AuthorizationError(
'O cabeçalho Authorization está ausente (Authorization header is missing)',
401
);
}

if (!authorization.startsWith(bearerPrefix)) {
throw new AuthorizationError(
`O cabeçalho Authorization deve começar com "${bearerPrefix}" (Authorization header must start with "${bearerPrefix}")`,
401
);
}

return authorization.slice(bearerPrefix.length);
}

Passo 2: Recupere informações sobre seu tenant Logto

Você precisará dos seguintes valores para validar tokens emitidos pelo Logto:

  • JSON Web Key Set (JWKS) URI: A URL das chaves públicas do Logto, usada para verificar assinaturas de JWT.
  • Emissor (Issuer): O valor esperado do emissor (URL OIDC do Logto).

Primeiro, encontre o endpoint do seu tenant Logto. Você pode encontrá-lo em vários lugares:

  • No Logto Console, em ConfiguraçõesDomínios.
  • Em qualquer configuração de aplicativo que você configurou no Logto, ConfiguraçõesEndpoints & Credenciais.

Buscar no endpoint de descoberta do OpenID Connect

Esses valores podem ser recuperados do endpoint de descoberta do OpenID Connect do Logto:

https://<seu-endpoint-logto>/oidc/.well-known/openid-configuration

Aqui está um exemplo de resposta (outros campos omitidos para brevidade):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Como o Logto não permite personalizar o JWKS URI ou o emissor, você pode definir esses valores manualmente no seu código. No entanto, isso não é recomendado para aplicações em produção, pois pode aumentar o esforço de manutenção caso alguma configuração mude no futuro.

  • JWKS URI: https://<seu-endpoint-logto>/oidc/jwks
  • Emissor: https://<seu-endpoint-logto>/oidc

Passo 3: Valide o token e as permissões

Após extrair o token e buscar a configuração OIDC, valide o seguinte:

  • Assinatura: O JWT deve ser válido e assinado pelo Logto (via JWKS).
  • Emissor: Deve corresponder ao emissor do seu tenant Logto.
  • Público (Audience): Deve corresponder ao indicador de recurso da API registrado no Logto, ou ao contexto de organização se aplicável.
  • Expiração: O token não pode estar expirado.
  • Permissões (escopos): O token deve incluir os escopos necessários para sua API / ação. Os escopos são strings separadas por espaço na reivindicação scope.
  • Contexto de organização: Se estiver protegendo recursos de API em nível de organização, valide a reivindicação organization_id.

Veja JSON Web Token para saber mais sobre a estrutura e reivindicações do JWT.

O que verificar para cada modelo de permissão

As reivindicações e regras de validação diferem conforme o modelo de permissão:

Modelo de permissãoReivindicação de público (aud)Reivindicação de organização (organization_id)Escopos (permissões) a verificar (scope)
Recursos globais de APIIndicador de recurso de APINão presentePermissões do recurso de API
Permissões de organização (não-API)urn:logto:organization:<id> (contexto de organização em aud)Não presentePermissões da organização
Recursos de API em nível de organizaçãoIndicador de recurso de APIID da organização (deve corresponder à requisição)Permissões do recurso de API

Para permissões de organização não-API, o contexto de organização é representado pela reivindicação aud (por exemplo, urn:logto:organization:abc123). A reivindicação organization_id só está presente para tokens de recursos de API em nível de organização.

dica:

Sempre valide tanto as permissões (escopos) quanto o contexto (público, organização) para APIs multi-tenant seguras.

Adicione a lógica de validação

Usamos jose neste exemplo para validar o JWT. Instale-o se ainda não instalou:

npm install jose

Ou use seu gerenciador de pacotes preferido (por exemplo, pnpm ou yarn).

Primeiro, adicione estas utilidades compartilhadas para lidar com a validação do JWT:

jwt-validator.ts
import { createRemoteJWKSet, jwtVerify, JWTPayload } from 'jose';
import { AuthInfo, AuthorizationError } from './auth-middleware.js';

const jwks = createRemoteJWKSet(new URL(JWKS_URI));

export async function validateJwtToken(token: string): Promise<JWTPayload> {
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
});

verifyPayload(payload);
return payload;
}

export function createAuthInfo(payload: JWTPayload): AuthInfo {
const scopes = (payload.scope as string)?.split(' ') ?? [];
const audience = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];

return new AuthInfo(
payload.sub!,
payload.client_id as string,
payload.organization_id as string,
scopes,
audience
);
}

function verifyPayload(payload: JWTPayload): void {
// Implemente sua lógica de verificação aqui com base no modelo de permissão
// Isso será mostrado na seção de modelos de permissão abaixo
}

Em seguida, implemente o middleware para verificar o token de acesso (Access token):

auth-middleware.ts
import { Request, Response, NextFunction } from 'express';
import { extractBearerTokenFromHeaders, AuthorizationError } from './auth-middleware.js';
import { validateJwtToken, createAuthInfo } from './jwt-validator.js';

// Estenda a interface Request do Express para incluir auth
declare global {
namespace Express {
interface Request {
auth?: AuthInfo;
}
}
}

export async function verifyAccessToken(req: Request, res: Response, next: NextFunction) {
try {
const token = extractBearerTokenFromHeaders(req.headers);
const payload = await validateJwtToken(token);

// Armazene as informações de auth na requisição para uso genérico
req.auth = createAuthInfo(payload);

next();
} catch (err: any) {
return res.status(err.status ?? 401).json({ error: err.message });
}
}

De acordo com seu modelo de permissão, implemente a lógica de verificação apropriada em jwt-validator.ts:

jwt-validator.ts
function verifyPayload(payload: JWTPayload): void {
// Verifique se a reivindicação de público (audience) corresponde ao seu indicador de recurso de API
const audiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
if (!audiences.includes('https://your-api-resource-indicator')) {
throw new AuthorizationError('Público inválido');
}

// Verifique os escopos necessários para recursos globais de API
const requiredScopes = ['api:read', 'api:write']; // Substitua pelos escopos necessários
const scopes = (payload.scope as string)?.split(' ') ?? [];
if (!requiredScopes.every((scope) => scopes.includes(scope))) {
throw new AuthorizationError('Escopo insuficiente');
}
}

Passo 4: Aplique o middleware na sua API

Aplique o middleware nas rotas protegidas da sua API.

app.ts
import express from 'express';
import { verifyAccessToken } from './auth-middleware.js';

const app = express();

app.get('/api/protected', verifyAccessToken, (req, res) => {
// Acesse as informações de autenticação diretamente de req.auth
res.json({ auth: req.auth });
});

app.get('/api/protected/detailed', verifyAccessToken, (req, res) => {
// Sua lógica de endpoint protegido
res.json({
auth: req.auth,
message: 'Dados protegidos acessados com sucesso',
});
});

app.listen(3000);

Passo 5: Teste sua implementação

Teste sua API com tokens válidos e inválidos para garantir que:

  • Tokens válidos passam e concedem acesso.
  • Retorne 401 Unauthorized para tokens inválidos / ausentes. Retorne 403 Forbidden para tokens válidos que não possuem as permissões ou contexto necessários.
Personalizando reivindicações de token JSON Web Token (JWT)

OpenID Connect Discovery

RFC 8707: Indicadores de recurso