Saltar al contenido principal

Cómo validar tokens de acceso en tu servicio API o backend

Validar tokens de acceso es una parte fundamental para aplicar el control de acceso basado en roles (RBAC) en Logto. Esta guía te guía a través de la verificación de JWTs emitidos por Logto en tu backend / API, comprobando la firma, emisor, audiencia, expiración, permisos (alcances) y contexto de organización.

Antes de comenzar

Paso 1: Inicializar constantes y utilidades

Define las constantes y utilidades necesarias en tu código para manejar la extracción y validación de tokens. Una solicitud válida debe incluir un encabezado Authorization en la forma Bearer <token de acceso (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('Authorization header is missing', 401);
}

if (!authorization.startsWith(bearerPrefix)) {
throw new AuthorizationError(`Authorization header must start with "${bearerPrefix}"`, 401);
}

return authorization.slice(bearerPrefix.length);
}

Paso 2: Recuperar información sobre tu tenant de Logto

Necesitarás los siguientes valores para validar los tokens emitidos por Logto:

  • URI de JSON Web Key Set (JWKS): La URL a las claves públicas de Logto, utilizada para verificar las firmas de JWT.
  • Emisor (Issuer): El valor esperado del emisor (la URL OIDC de Logto).

Primero, encuentra el endpoint de tu tenant de Logto. Puedes encontrarlo en varios lugares:

  • En la Consola de Logto, en ConfiguraciónDominios.
  • En cualquier configuración de aplicación que hayas configurado en Logto, ConfiguraciónEndpoints y Credenciales.

Obtener desde el endpoint de descubrimiento de OpenID Connect

Estos valores pueden obtenerse desde el endpoint de descubrimiento de OpenID Connect de Logto:

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

Aquí tienes un ejemplo de respuesta (otros campos omitidos por brevedad):

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

Dado que Logto no permite personalizar la URI de JWKS ni el emisor, puedes codificar estos valores directamente en tu código. Sin embargo, esto no se recomienda para aplicaciones en producción, ya que puede aumentar la carga de mantenimiento si alguna configuración cambia en el futuro.

  • URI de JWKS: https://<your-logto-endpoint>/oidc/jwks
  • Emisor: https://<your-logto-endpoint>/oidc

Paso 3: Validar el token y los permisos

Después de extraer el token y obtener la configuración OIDC, valida lo siguiente:

  • Firma: El JWT debe ser válido y estar firmado por Logto (a través de JWKS).
  • Emisor (Issuer): Debe coincidir con el emisor de tu tenant de Logto.
  • Audiencia (Audience): Debe coincidir con el indicador de recurso de la API registrado en Logto, o el contexto de la organización si corresponde.
  • Expiración: El token no debe estar expirado.
  • Permisos (Alcances / scopes): El token debe incluir los alcances requeridos para tu API / acción. Los alcances son cadenas separadas por espacios en el reclamo scope.
  • Contexto de organización: Si proteges recursos de API a nivel de organización, valida el reclamo organization_id.

Consulta JSON Web Token para aprender más sobre la estructura y los reclamos de JWT.

Qué verificar para cada modelo de permisos

  • Reclamo de audiencia (aud): Indicador de recurso de API
  • Reclamo de organización (organization_id): No presente
  • Alcances (permisos) a verificar (scope): Permisos de recurso de API

Para los permisos de organización que no son de API, el contexto de la organización está representado por el reclamo aud (por ejemplo, urn:logto:organization:abc123). El reclamo organization_id solo está presente para tokens de recursos de API a nivel de organización.

tip:

Valida siempre tanto los permisos (alcances) como el contexto (audiencia, organización) para APIs multi-tenant seguras.

Añadir la lógica de validación

Usamos jose en este ejemplo para validar el JWT. Instálalo si aún no lo has hecho:

npm install jose

O utiliza tu gestor de paquetes preferido (por ejemplo, pnpm o yarn).

Primero, añade estas utilidades compartidas para manejar la validación de 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 validateJwt(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 {
// Implementa aquí tu lógica de verificación basada en el modelo de permisos
// Esto se mostrará en la sección de modelos de permisos a continuación
}

Luego, implementa el middleware para verificar el token de acceso:

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

// Extiende la interfaz Request de 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 validateJwt(token);

// Almacena la información de autenticación en la solicitud para uso genérico
req.auth = createAuthInfo(payload);

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

De acuerdo con tu modelo de permisos, implementa la lógica de verificación adecuada en jwt-validator.ts:

jwt-validator.ts
function verifyPayload(payload: JWTPayload): void {
// Verifica que el reclamo de audiencia coincida con tu 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('Audiencia no válida');
}

// Verifica los alcances requeridos para recursos de API globales
const requiredScopes = ['api:read', 'api:write']; // Reemplaza con tus alcances requeridos reales
const scopes = (payload.scope as string)?.split(' ') ?? [];
if (!requiredScopes.every((scope) => scopes.includes(scope))) {
throw new AuthorizationError('Alcance insuficiente');
}
}

Paso 4: Aplica el middleware a tu API

Aplica el middleware a tus rutas de API protegidas.

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

const app = express();

app.get('/api/protected', verifyAccessToken, (req, res) => {
// Accede a la información de autenticación directamente desde req.auth
res.json({ auth: req.auth });
});

app.get('/api/protected/detailed', verifyAccessToken, (req, res) => {
// Tu lógica para el endpoint protegido
res.json({
auth: req.auth,
message: 'Datos protegidos accedidos correctamente',
});
});

app.listen(3000);

Paso 5: Prueba tu implementación

Obtener tokens de acceso (Access tokens)

Desde tu aplicación cliente: Si has configurado una integración de cliente, tu aplicación puede obtener tokens automáticamente. Extrae el token de acceso y úsalo en las solicitudes a la API.

Para pruebas con curl / Postman:

  1. Tokens de usuario: Usa las herramientas de desarrollador de tu aplicación cliente para copiar el token de acceso desde localStorage o la pestaña de red.

  2. Tokens máquina a máquina: Utiliza el flujo de credenciales de cliente. Aquí tienes un ejemplo no normativo usando curl:

    curl -X POST https://your-tenant.logto.app/oidc/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=your-m2m-client-id" \
    -d "client_secret=your-m2m-client-secret" \
    -d "resource=https://your-api-resource-indicator" \
    -d "scope=api:read api:write"

    Puede que necesites ajustar los parámetros resource y scope según tu recurso de API y permisos; también puede ser necesario un parámetro organization_id si tu API está asociada a una organización.

tip:

¿Necesitas inspeccionar el contenido del token? Usa nuestro decodificador de JWT para decodificar y verificar tus JWTs.

Probar endpoints protegidos

Solicitud con token válido
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected

Respuesta esperada:

{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Token ausente
curl http://localhost:3000/api/protected

Respuesta esperada (401):

{
"error": "Authorization header is missing"
}
Token inválido
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected

Respuesta esperada (401):

{
"error": "Invalid token"
}

Pruebas específicas del modelo de permisos

Escenarios de prueba para APIs protegidas con alcances globales:

  • Alcances válidos: Prueba con tokens que incluyan los alcances de API requeridos (por ejemplo, api:read, api:write)
  • Alcances ausentes: Espera 403 Prohibido cuando el token no tenga los alcances requeridos
  • Audiencia incorrecta: Espera 403 Prohibido cuando la audiencia no coincida con el recurso de API
# Token sin los alcances requeridos - espera 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected
Personalización de reclamos de token JSON Web Token (JWT)

OpenID Connect Discovery

RFC 8707: Indicadores de recurso