🔑 OAuth 2.0

OAuth 2.0 es el marco de autorización más utilizado para el acceso a la API. Seleccionar el tipo de concesión adecuado es fundamental.

Elegir un tipo de subvención

Tipo de subvención Caso práctico Nivel de seguridad
Authorization Code + PKCE SPA y aplicaciones móviles (recomendado) Más alto
Authorization Code Aplicaciones web del lado del servidor Alta
Client Credentials Comunicación de servidor a servidor (M2M) Medio
Implícito (obsoleto) SPA heredados Bajo
Contraseña del propietario del recurso (obsoleta) Sólo de primera mano y de gran confianza Bajo
No utilizar la subvención implícita

Implicit Grant está ahora obsoleto porque el token se expone en el fragmento de URL. Utilice Código de autorización + PKCE para SPA.

Código de autorización + Flujo PKCE

JavaScript Generación de retos PKCE
// 1. Generar aleatoriamente code_verifier
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return btoa(String.fromCharCode(...array))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 2. Generar code_challenge con SHA-256
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

// 3. Solicitud de autorización
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);

const authUrl = `https://auth.example.com/authorize?` +
  `response_type=code&` +
  `client_id=${clientId}&` +
  `redirect_uri=${redirectUri}&` +
  `code_challenge=${challenge}&` +
  `code_challenge_method=S256&` +
  `scope=read write`;

🎫 JWT (JSON Web Token)

JWT se utiliza ampliamente como token de autenticación sin estado, pero los errores de implementación conducen directamente a vulnerabilidades de seguridad.

Consideraciones de seguridad JWT

✅ Do

  • Especifique explícitamente el algoritmo (HS256 / RS256)
  • Establezca un tiempo de caducidad (exp) corto (15 minutos o menos)
  • Validar emisor (iss) y audiencia (aud)
  • Generar claves secretas de longitud suficiente (256 bits o más)
  • Rotación de las fichas de actualización

❌ No

  • Almacenar información sensible en la carga JWT (Base64 no es cifrado)
  • Permitir alg: "ninguno"
  • Almacenar tokens en localStorage (vulnerable a XSS)
  • Descuidar la gestión de la revocación de tokens
  • Utilizar una clave pública RS256 como clave secreta HS256

Implementación segura de JWT

JavaScript (Node.js) Emisión y verificación de tokens
const jwt = require('jsonwebtoken');

// --- Emisión de tokens ---
function issueTokens(user) {
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET,
    {
      algorithm: 'HS256',
      expiresIn: '15m',
      issuer: 'api.example.com',
      audience: 'app.example.com',
    }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    process.env.REFRESH_SECRET,
    { algorithm: 'HS256', expiresIn: '7d' }
  );

  return { accessToken, refreshToken };
}

// Verificación de tokens
function verifyAccessToken(token) {
  return jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],   // ← ¡Especificar siempre!
    issuer: 'api.example.com',
    audience: 'app.example.com',
  });
}

Comparación de almacenes de fichas

Almacenamiento Resistencia XSS Resistencia a CSRF Recomendación
HttpOnly Cookie △ (requiere configuración SameSite) Recomendado
Memoria (variable) Pérdida de navegación en la página
localStorage ✕ (puede ser robado mediante XSS). No recomendado
sessionStorage ✕ (puede ser robado mediante XSS). Uso limitado
Recomendado: HttpOnly + Seguro + Cookie SameSite

Almacenar el token de acceso en una cookie con los atributos HttpOnly, Secure y SameSite=Strict es el enfoque más seguro. Esto evita el robo del token mediante ataques XSS.

🗝 Gestión de claves API

Las claves API son un mecanismo de autenticación sencillo, pero una gestión inadecuada puede acarrear graves riesgos.

Prácticas recomendadas para las claves API

Generación y almacenamiento

Generar con suficiente entropía (256 bits o más). Almacenar con hash; nunca guardar texto plano en la base de datos.

Restricción del ámbito de aplicación

Asigne ámbitos mínimos por clave (sólo lectura, sólo recursos específicos, etc.). Evita los permisos comodín.

Rotación

Rote las llaves con regularidad (en un plazo de 90 días). Establezca un periodo de gracia para las llaves antiguas. Revóquelas inmediatamente en caso de fuga.

Método de transmisión

Enviar mediante cabecera (X-API-Key o Authorization). No incluir en parámetros de consulta de URL (aparecen en los registros).

Gestión de variables de entorno

No codifique las claves en el código fuente. Adminístrelas con variables de entorno o un gestor de secretos.

Supervisión y registro

Supervisar el uso de la clave API. Detecte patrones anómalos (solicitudes masivas, accesos desde IP desconocidas).

Ejemplo de aplicación de la validación de claves API

JavaScript (Express) Validación de la clave hash de la API
const crypto = require('crypto');

// Hash de la clave API con SHA-256 y comparar
async function validateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  // Hash y buscar en DB (timingSafeEqual no es necesario ya que comparamos hashes)
  const hashedKey = crypto
    .createHash('sha256')
    .update(apiKey)
    .digest('hex');

  const keyRecord = await db.findApiKey(hashedKey);

  if (!keyRecord || keyRecord.revokedAt) {
    return res.status(403).json({ error: 'Invalid API key' });
  }

  // Comprobar caducidad
  if (keyRecord.expiresAt && new Date() > keyRecord.expiresAt) {
    return res.status(403).json({ error: 'API key expired' });
  }

  req.apiClient = keyRecord;
  next();
}

🤖 Securing LLM & AI Agent API Access

AI agents and LLM-powered applications require additional authentication and authorization patterns beyond traditional API security.

Model-Specific Scopes

Assign different access levels per model tier (e.g., GPT-4 requires elevated scope). Prevent unauthorized use of expensive or sensitive models.

Token Budget Enforcement

Embed token budgets in access tokens or API key metadata. Enforce per-request and per-day limits to prevent denial-of-wallet attacks.

Prompt-Level Authorization

Check user permissions before executing certain prompt types (e.g., code generation, data analysis). Map prompt categories to role-based permissions.

Multi-Tenant Isolation

Ensure conversation history, fine-tuned models, and RAG data are isolated per tenant. Use tenant-scoped API keys with namespace enforcement.

Agent Identity & Attestation

Assign cryptographic identities to AI agents. Use signed JWTs for inter-agent communication. Verify agent identity before granting tool access.

Credential Rotation for AI Services

Automate API key rotation for LLM providers. Use short-lived tokens for agent sessions. Revoke credentials immediately when agents are decommissioned.

Agent Tool Access Authorization

Scenario Auth Method Required Scope Approval
Agent reads public data API Key data:read Automatic
Agent modifies user data OAuth 2.0 (delegated) data:write User consent required
Agent calls external API OAuth 2.0 + mTLS external:invoke Human-in-the-loop
Agent executes code / shell Scoped JWT + Sandbox exec:sandbox Admin approval + audit log

Scoped OAuth Token for AI Agents (Python)

Python Agent Token Issuance with Scope Enforcement
import jwt
import time
from datetime import datetime, timedelta

class AgentTokenIssuer:
    """Issues scoped, short-lived tokens for AI agents."""

    ALLOWED_SCOPES = {
        "reader": ["data:read"],
        "writer": ["data:read", "data:write"],
        "executor": ["data:read", "data:write", "exec:sandbox"],
    }

    def __init__(self, secret_key: str):
        self.secret_key = secret_key

    def issue_agent_token(
        self,
        agent_id: str,
        role: str,
        tenant_id: str,
        token_budget: int = 10000,
        ttl_minutes: int = 15,
    ) -> str:
        # Validate role and resolve scopes
        if role not in self.ALLOWED_SCOPES:
            raise ValueError(f"Invalid role: {role}")

        payload = {
            "sub": agent_id,
            "tenant": tenant_id,
            "scopes": self.ALLOWED_SCOPES[role],
            "token_budget": token_budget,
            "iat": datetime.utcnow(),
            "exp": datetime.utcnow() + timedelta(minutes=ttl_minutes),
            "type": "agent",
        }
        return jwt.encode(payload, self.secret_key, algorithm="HS256")

    def verify_tool_access(self, token: str, required_scope: str) -> dict:
        # Decode and verify agent token
        payload = jwt.decode(
            token, self.secret_key, algorithms=["HS256"]
        )
        if required_scope not in payload["scopes"]:
            raise PermissionError(
                f"Agent {payload['sub']} lacks scope: {required_scope}"
            )
        return payload
OWASP References

Related: LLM06: Excessive Agency, ASI01: Excessive Agency, ASI03: Insecure Tool/Function Calling

Comparación de métodos de autenticación

Método Sin estado Caso práctico Consideraciones de seguridad
OAuth 2.0 + PKCE API que requieren autorización del usuario estado/PKCE requerido, gestión de la caducidad de tokens
JWT Bearer Comunicación entre microservicios Se requiere especificación del algoritmo, no hay datos sensibles en la carga útil
Clave API Servidor a servidor, integraciones externas Rotación y restricción del alcance requeridas
mTLS Comunicación M2M de alta seguridad Elevado coste operativo de la gestión de certificados
Cookie de sesión Aplicaciones web tradicionales Protección CSRF necesaria, problemas de escalabilidad