logo
Desarrollo
Buscar
05 · Verificación de token y seguridad

05 · Verificación de token y seguridad

Ya sea en modo push o pull, al final el desarrollador siempre obtiene un wsa (JWT HS256). Este documento es de lectura obligatoria para todos los integradores: una verificación laxa = una identidad falsificable.

Lista de verificación obligatoria

Tras obtener el wsa, verifica en el servicio de backend de la app de extensión en el siguiente orden (verifyWsa lo incorpora todo internamente):

  1. Firma: verifica con HS256 usando la clave distribuida/registrada por la plataforma. Si falla → rechazar.
  2. exp: el tiempo actual ≤ exp (incluyendo un pequeño leeway). El exp debe existir: un token sin exp debe rechazarse directamente (de lo contrario, equivale a que nunca expira).
  3. iss: debe ser igual a gptbots-workspace.
  4. aud: debe ser igual al host de la app de extensión de destino. Esta es la línea de defensa clave para evitar que «el token sea robado para atacar otra app», y no puede omitirse.
  5. iat / nbf (si existen): no deben estar en el futuro (más allá del leeway), para evitar que un token con «el momento de emisión fijado en un futuro lejano» pueda usarse durante mucho tiempo.
  6. Segmentación por workspace_id: si la extensión implementa aislamiento entre varios espacios de trabajo, asigna la petición a ese workspace_id y prohíbe el acceso entre tenants.
  7. Elimina de inmediato el wsa / code de la URL tras el aterrizaje: con history.replaceState, para evitar que el usuario comparta el token al copiar la URL.
  8. Establece la sesión propia: canjéalo por la sesión/cookie de la propia app de extensión, y las peticiones posteriores no deben depender del wsa, que expira en 5 minutos.

El verifyWsa del SDK oficial exige obligatoriamente que exista el exp, verifica la deriva futura de iat/nbf, y realiza una comparación de firma en tiempo constante y una lista blanca de algoritmos (por defecto solo HS256, para evitar ataques de confusión de alg). Al usarlo, cumples automáticamente los puntos 1–5.


Se recomienda verificar con el SDK

import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify'; try { const id = verifyWsa(wsa, { secret: process.env.EXTENSION_APP_SECRET, audience: 'app.example.com', }); // id: { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? } } catch (e) { if (e instanceof WsaVerificationError) { // e.code: InvalidToken | InvalidSignature | Expired | NotYetValid // | WrongIssuer | WrongAudience | MissingClaim | UnsupportedAlgorithm } }
                      
                      import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';

try {
  const id = verifyWsa(wsa, {
    secret: process.env.EXTENSION_APP_SECRET,
    audience: 'app.example.com',
  });
  // id: { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? }
} catch (e) {
  if (e instanceof WsaVerificationError) {
    // e.code: InvalidToken | InvalidSignature | Expired | NotYetValid
    //       | WrongIssuer | WrongAudience | MissingClaim | UnsupportedAlgorithm
  }
}

                    
Este bloque de código en una ventana flotante

Las opciones completas y los códigos de error están en Referencia de la API del SDK.

Verificación sin SDK (en varios lenguajes)

El wsa es un JWT HS256 estándar, y cualquier librería JWT puede verificarlo. Asegúrate de activar explícitamente la verificación de iss / aud y de fijar algorithms=['HS256'].

Java (auth0 java-jwt)

JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SHARED_SECRET)) .withIssuer("gptbots-workspace") .withAudience("app.example.com") .acceptLeeway(30) // tolera 30s de deriva del reloj .build(); DecodedJWT jwt = verifier.verify(wsaParam); String userId = jwt.getSubject(); String workspaceId = jwt.getClaim("workspace_id").asString(); String role = jwt.getClaim("role").asString(); String username = jwt.getClaim("username").asString(); // puede ser null String email = jwt.getClaim("email").asString(); // puede ser null
                      
                      JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SHARED_SECRET))
    .withIssuer("gptbots-workspace")
    .withAudience("app.example.com")
    .acceptLeeway(30) // tolera 30s de deriva del reloj
    .build();

DecodedJWT jwt = verifier.verify(wsaParam);
String userId      = jwt.getSubject();
String workspaceId = jwt.getClaim("workspace_id").asString();
String role        = jwt.getClaim("role").asString();
String username    = jwt.getClaim("username").asString();  // puede ser null
String email       = jwt.getClaim("email").asString();     // puede ser null

                    
Este bloque de código en una ventana flotante

Node.js (jsonwebtoken)

const jwt = require('jsonwebtoken'); const payload = jwt.verify(wsaParam, SHARED_SECRET, { algorithms: ['HS256'], issuer: 'gptbots-workspace', audience: 'app.example.com', clockTolerance: 30, }); const { sub: userId, workspace_id, role, username, email, avatar } = payload;
                      
                      const jwt = require('jsonwebtoken');

const payload = jwt.verify(wsaParam, SHARED_SECRET, {
  algorithms: ['HS256'],
  issuer: 'gptbots-workspace',
  audience: 'app.example.com',
  clockTolerance: 30,
});
const { sub: userId, workspace_id, role, username, email, avatar } = payload;

                    
Este bloque de código en una ventana flotante

Python (PyJWT)

import jwt payload = jwt.decode( wsa_param, SHARED_SECRET, algorithms=["HS256"], issuer="gptbots-workspace", audience="app.example.com", leeway=30, ) user_id = payload["sub"] workspace_id = payload["workspace_id"] role = payload["role"]
                      
                      import jwt

payload = jwt.decode(
    wsa_param,
    SHARED_SECRET,
    algorithms=["HS256"],
    issuer="gptbots-workspace",
    audience="app.example.com",
    leeway=30,
)
user_id      = payload["sub"]
workspace_id = payload["workspace_id"]
role         = payload["role"]

                    
Este bloque de código en una ventana flotante

Recordatorio sobre la codificación de la clave: HS256 usa directamente la cadena de la clave como clave HMAC según sus bytes UTF-8. La clave per-app de Tier 2 tiene la forma wext_+64 hex; pásala como clave tal cual, en formato de cadena (no vuelvas a decodificarla en hex), para mantener la coherencia con el lado de firma de la plataforma.

Requisitos de seguridad (a implementar punto por punto)

  1. La clave es todo el modelo de seguridad. Actualmente HS256 es una clave simétrica: una vez filtrada, cualquiera puede falsificar la identidad de cualquier usuario del espacio de trabajo y atacar la app de extensión integrada. Colócala solo en el backend (variables de entorno/KMS); nunca la escribas en el código del frontend, en el repositorio de git, en los logs ni en la configuración del cliente.
  2. La superficie de exposición del JWT en la URL (solo en modo push): la query string queda registrada en el historial del navegador, en el access log del servidor web, en los logs de caché del CDN y en los registros del Referer. Aunque quede registrada, si se obtiene dentro de los 5 minutos, todavía puede aprovecharse. Elimínalo de inmediato con history.replaceState tras el aterrizaje (consumeHandoff ya lo hace por defecto). El modo pull (M-Auth) por naturaleza no coloca el wsa en la URL, lo que lo hace más seguro.
  3. No transmitas el wsa a subrecursos. Una vez canjeado por la sesión propia, las peticiones XHR/fetch/img posteriores no deben volver a llevar el wsa original; de lo contrario aparecería en el Referer de cada subrecurso.
  4. Sincronización del reloj. HS256 evalúa el exp de forma estricta, por lo que el reloj del servidor debe estar sincronizado por NTP; el leeway de 30s del ejemplo tolera pequeñas derivas, pero no lo amplíes al orden de minutos.
  5. El role solo sirve como mapeo de permisos por defecto. Solo refleja el rol del usuario en ese espacio de trabajo, no representa sus permisos dentro de tu app; tu app mantiene su propio modelo de permisos.
  6. Aislamiento multitenant. Usa siempre el workspace_id como clave de aislamiento de datos, para evitar que un usuario del espacio de trabajo A lea los datos del B.
  7. Emergencia de rotación/revocación de la clave: en la gestión del espacio, «rota la clave» de la app correspondiente, lo que generará una clave nueva que se mostrará una única vez; a continuación, reconfigura tu backend con la nueva clave.

5. Frontera de confianza del frontend (importante)

  • El frontend solo actúa como marcador de posición para mostrar datos, no toma decisiones de autorización. El frontend puede decodificar en base64 el payload del JWT, pero sin la clave no puede verificar la firma: cualquiera podría falsificar un payload «que parezca correcto».
  • Todas las decisiones sobre «quién es esta persona, si puede hacer algo o no» deben basarse en el resultado de verifyWsa del backend de la app de extensión.
  • Por eso, la estrategia estándar es: el frontend entrega el wsa/code al backend de la app de extensión → el backend verifica → el backend crea la sesión → el frontend solo reconoce esa sesión.

Siguiente paso: consulta Referencia de la API del SDK para ver la API completa; ante cualquier problema, consulta Preguntas frecuentes y solución de problemas.